diff --git a/src/parser.zig b/src/parser.zig index 54abcee..82e7f6f 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -16,6 +16,8 @@ pub const NodeType = enum { autoescape, spaceless, url, + cycle, + firstof, }; pub const AutoescapeNode = struct { @@ -25,6 +27,14 @@ pub const AutoescapeNode = struct { enabled: bool, }; +pub const FirstOfNode = struct { + values: []const []const u8, +}; + +pub const CycleNode = struct { + values: []const []const u8, +}; + pub const UrlNode = struct { name: []const u8, args: []const []const u8, @@ -129,6 +139,8 @@ pub const Node = struct { autoescape: ?AutoescapeNode = null, spaceless: ?SpacelessNode = null, url: ?UrlNode = null, + cycle: ?CycleNode = null, + firstof: ?FirstOfNode = null, pub fn deinit(self: Node, allocator: std.mem.Allocator) void { switch (self.type) { @@ -205,6 +217,14 @@ pub const Node = struct { for (u.args) |a| allocator.free(a); allocator.free(u.args); }, + .cycle => if (self.cycle) |c| { + for (c.values) |v| allocator.free(v); + allocator.free(c.values); + }, + .firstof => if (self.firstof) |fo| { + for (fo.values) |v| allocator.free(v); + allocator.free(fo.values); + }, } } }; @@ -1289,6 +1309,111 @@ pub const Parser = struct { continue; } + if (std.mem.eql(u8, tag_name, "cycle")) { + const args = node.tag.?.args; + + var values = std.ArrayList([]const u8){}; + defer values.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + var in_quote = false; + var quote_char: u8 = 0; + if (args[i] == '"' or args[i] == '\'') { + in_quote = true; + quote_char = args[i]; + i += 1; + } + + while (i < args.len) { + if (in_quote) { + if (args[i] == quote_char) { + i += 1; + break; + } + } else { + if (std.ascii.isWhitespace(args[i])) break; + } + i += 1; + } + + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, try allocator.dupe(u8, value)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um cycle\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .cycle, + .cycle = .{ + .values = try values.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "firstof")) { + const args = node.tag.?.args; + + var values = std.ArrayList([]const u8){}; + defer values.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + var in_quote = false; + var quote_char: u8 = 0; + if (args[i] == '"' or args[i] == '\'') { + in_quote = true; + quote_char = args[i]; + i += 1; + } + + while (i < args.len) { + if (in_quote) { + if (args[i] == quote_char) { + i += 1; + break; + } + } else { + if (std.ascii.isWhitespace(args[i])) break; + } + i += 1; + } + + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, try allocator.dupe(u8, value)); + + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um firstof\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .firstof, + .firstof = .{ + .values = try values.toOwnedSlice(allocator), + }, + }); + continue; + } + // Para tags normais std.debug.print("===================================\n", .{}); try list.append(allocator, node); diff --git a/src/parser_test.zig b/src/parser_test.zig index 373387b..b9643e6 100644 --- a/src/parser_test.zig +++ b/src/parser_test.zig @@ -2,535 +2,610 @@ const std = @import("std"); const testing = std.testing; const parser = @import("parser.zig"); -// test "parse texto simples" { -// const allocator = testing.allocator; -// const template = "Olá mundo!"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Olá mundo!", nodes[0].text.?.content); -// } -// -// test "parse variável simples" { -// const allocator = testing.allocator; -// const template = "Olá {{ nome }}!"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 3), nodes.len); -// -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Olá ", nodes[0].text.?.content); -// -// try testing.expect(nodes[1].type == .variable); -// try testing.expectEqualStrings("nome", nodes[1].variable.?.expr); -// -// try testing.expect(nodes[2].type == .text); -// try testing.expectEqualStrings("!", nodes[2].text.?.content); -// } -// -// test "parse variável com espaços" { -// const allocator = testing.allocator; -// const template = "{{ espacos }}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .variable); -// try testing.expectEqualStrings("espacos", nodes[0].variable.?.expr); -// } -// -// test "parse tag simples" { -// const allocator = testing.allocator; -// const template = "Antes {% minha_tag %} Depois"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 3), nodes.len); -// -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Antes ", nodes[0].text.?.content); -// -// try testing.expect(nodes[1].type == .tag); -// try testing.expectEqualStrings("minha_tag", nodes[1].tag.?.name); -// try testing.expectEqualStrings("", nodes[1].tag.?.args); -// -// try testing.expect(nodes[2].type == .text); -// try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); -// } -// -// test "parse if block básico" { -// const allocator = testing.allocator; -// const template = "{% if usuario.logado %}Bem-vindo!{% endif %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .if_block); -// -// const ib = nodes[0].@"if".?; -// try testing.expectEqualStrings("usuario.logado", ib.condition); -// try testing.expectEqual(@as(usize, 1), ib.true_body.len); -// try testing.expect(nodes[0].@"if".?.true_body[0].type == .text); -// try testing.expectEqualStrings("Bem-vindo!", ib.true_body[0].text.?.content); -// try testing.expectEqual(@as(usize, 0), ib.false_body.len); -// } -// -// test "parse if block sem else" { -// const allocator = testing.allocator; -// const template = "{% if cond %}Verdadeiro{% endif %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .if_block); -// const ib = nodes[0].@"if".?; -// try testing.expectEqualStrings("cond", ib.condition); -// try testing.expectEqual(@as(usize, 1), ib.true_body.len); -// try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); -// try testing.expectEqual(@as(usize, 0), ib.false_body.len); -// } -// -// test "parse if block com else" { -// const allocator = testing.allocator; -// const template = "{% if cond %}Verdadeiro{% else %}Falso{% endif %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .if_block); -// const ib = nodes[0].@"if".?; -// try testing.expectEqualStrings("cond", ib.condition); -// try testing.expectEqual(@as(usize, 1), ib.true_body.len); -// try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); -// try testing.expectEqual(@as(usize, 1), ib.false_body.len); -// try testing.expectEqualStrings("Falso", ib.false_body[0].text.?.content); -// } -// -// test "parse for block sem empty" { -// const allocator = testing.allocator; -// const template = "{% for item in lista %}Item: {{ item }}{% endfor %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .for_block); -// const fb = nodes[0].@"for".?; -// try testing.expectEqualStrings("item", fb.loop_var); -// try testing.expectEqualStrings("lista", fb.iterable); -// try testing.expectEqual(@as(usize, 2), fb.body.len); // <--- corrigido: 2 nós -// try testing.expectEqual(@as(usize, 0), fb.empty_body.len); -// -// try testing.expect(fb.body[0].type == .text); -// try testing.expectEqualStrings("Item: ", fb.body[0].text.?.content); -// try testing.expect(fb.body[1].type == .variable); -// try testing.expectEqualStrings("item", fb.body[1].variable.?.expr); -// } -// -// test "parse for block com empty" { -// const allocator = testing.allocator; -// const template = "{% for item in lista %}Tem{% empty %}Vazio{% endfor %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .for_block); -// const fb = nodes[0].@"for".?; -// try testing.expectEqual(@as(usize, 1), fb.body.len); -// try testing.expectEqualStrings("Tem", fb.body[0].text.?.content); -// try testing.expectEqual(@as(usize, 1), fb.empty_body.len); -// try testing.expectEqualStrings("Vazio", fb.empty_body[0].text.?.content); -// } -// -// test "parse comment" { -// const allocator = testing.allocator; -// const template = "Bazinga! {% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Bazinga! ", nodes[0].text.?.content); -// } -// -// test "parse include simples" { -// const allocator = testing.allocator; -// const template = "Cabeçalho {% include \"header.zdt\" %} Conteúdo {% include \"footer.zdt\" %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 4), nodes.len); -// -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Cabeçalho ", nodes[0].text.?.content); -// -// try testing.expect(nodes[1].type == .include); -// try testing.expectEqualStrings("header.zdt", nodes[1].include.?.template_name); -// -// try testing.expect(nodes[2].type == .text); -// try testing.expectEqualStrings(" Conteúdo ", nodes[2].text.?.content); -// -// try testing.expect(nodes[3].type == .include); -// try testing.expectEqualStrings("footer.zdt", nodes[3].include.?.template_name); -// -// } -// -// test "parse include sem aspas (erro esperado no futuro, mas por enquanto aceita)" { -// const allocator = testing.allocator; -// const template = "{% include header.zdt %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .include); -// try testing.expectEqualStrings("header.zdt", nodes[0].include.?.template_name); -// } -// -// test "parse with simples" { -// const allocator = testing.allocator; -// const template = "{% with nome=\"Lucas\" idade=30 %}Olá {{ nome }}, você tem {{ idade }} anos.{% endwith %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .with_block); -// const w = nodes[0].with.?; -// try testing.expectEqual(@as(usize, 2), w.assignments.len); -// try testing.expectEqual(@as(usize, 5), w.body.len); -// try testing.expect(w.body[0].type == .text); -// } -// -// test "parse now simples" { -// const allocator = testing.allocator; -// const template = "Data atual: {% now \"Y-m-d\" %} às {% now \"H:i\" %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 4), nodes.len); -// -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Data atual: ", nodes[0].text.?.content); -// -// try testing.expect(nodes[1].type == .now); -// try testing.expectEqualStrings("Y-m-d", nodes[1].now.?.format); -// -// try testing.expect(nodes[2].type == .text); -// try testing.expectEqualStrings(" às ", nodes[2].text.?.content); -// -// try testing.expect(nodes[3].type == .now); -// try testing.expectEqualStrings("H:i", nodes[3].now.?.format); -// -// } -// -// test "parse now sem aspas" { -// const allocator = testing.allocator; -// const template = "{% now Y-m-d %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .now); -// try testing.expectEqualStrings("Y-m-d", nodes[0].now.?.format); -// } -// -// test "parse extends" { -// const allocator = testing.allocator; -// const template = "{% extends \"base.zdt\" %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .extends); -// try testing.expectEqualStrings("base.zdt", nodes[0].extends.?.parent_name); -// } -// -// test "parse block simples" { -// const allocator = testing.allocator; -// const template = "{% block titulo %}Meu Título{% endblock %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .block); -// const b = nodes[0].block.?; -// try testing.expectEqualStrings("titulo", b.name); -// try testing.expectEqual(@as(usize, 1), b.body.len); -// try testing.expectEqualStrings("Meu Título", b.body[0].text.?.content); -// } -// -// test "parse block com super" { -// const allocator = testing.allocator; -// const template = "{% block conteudo %}{{ block.super }} Conteúdo filho{% endblock %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .block); -// const b = nodes[0].block.?; -// try testing.expectEqual(@as(usize, 2), b.body.len); -// try testing.expect(b.body[0].type == .super); -// // try testing.expectEqualStrings("conteudo", b.name); -// try testing.expectEqualStrings(" Conteúdo filho", b.body[1].text.?.content); -// } -// -// -// test "parse filter block simples" { -// const allocator = testing.allocator; -// const template = "{% filter upper %}olá mundo{% endfilter %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .filter_block); -// const fb = nodes[0].filter_block.?; -// try testing.expectEqualStrings("upper", fb.filters); // correto -// try testing.expectEqual(@as(usize, 1), fb.body.len); -// try testing.expectEqualStrings("olá mundo", fb.body[0].text.?.content); -// } -// -// test "parse filter block com múltiplos filtros" { -// const allocator = testing.allocator; -// const template = "{% filter upper|escape %}Conteúdo negrito{% endfilter %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .filter_block); -// const fb = nodes[0].filter_block.?; -// try testing.expectEqualStrings("upper|escape", fb.filters); -// try testing.expectEqual(@as(usize, 1), fb.body.len); -// try testing.expectEqualStrings("Conteúdo negrito", fb.body[0].text.?.content); -// } -// -// test "parse variable com filtros" { -// const allocator = testing.allocator; -// const template = "{{ nome|upper|default:\"Visitante\" }}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .variable); -// const v = nodes[0].variable.?; -// try testing.expectEqualStrings("nome", v.expr); -// try testing.expectEqual(@as(usize, 2), v.filters.len); -// try testing.expectEqualStrings("upper", v.filters[0].name); -// try testing.expect(v.filters[0].arg == null); -// try testing.expectEqualStrings("default", v.filters[1].name); -// try testing.expectEqualStrings("Visitante", v.filters[1].arg.?); -// } -// -// test "parse autoescape on" { -// const allocator = testing.allocator; -// const template = "{% autoescape on %}Texto negrito{% endautoescape %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .autoescape); -// const ae = nodes[0].autoescape.?; -// try testing.expect(ae.enabled == true); -// try testing.expectEqual(@as(usize, 1), ae.body.len); -// try testing.expectEqualStrings("Texto negrito", ae.body[0].text.?.content); -// } -// -// test "parse autoescape off" { -// const allocator = testing.allocator; -// const template = "{% autoescape off %}Texto negrito{% endautoescape %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .autoescape); -// const ae = nodes[0].autoescape.?; -// try testing.expect(ae.enabled == false); -// try testing.expectEqual(@as(usize, 1), ae.body.len); -// } -// -// test "parse spaceless simples" { -// const allocator = testing.allocator; -// const template = "{% spaceless %}
Texto com espaços
{% endspaceless %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .spaceless); -// const sl = nodes[0].spaceless.?; -// try testing.expectEqual(@as(usize, 1), sl.body.len); -// try testing.expectEqualStrings("Texto com espaços
", sl.body[0].text.?.content); -// } -// -// test "parse spaceless aninhado" { -// const allocator = testing.allocator; -// const template = "{% spaceless %}Outer {% spaceless %}Inner{% endspaceless %} Outer{% endspaceless %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .spaceless); -// const sl = nodes[0].spaceless.?; -// for (sl.body) |b| { -// std.debug.print("type: {s}\n", .{@tagName(b.type)}); -// if (b.type == .spaceless) { -// std.debug.print(" - tag -> spaceless\n", .{}); -// } -// if(b.type == .text) { -// std.debug.print(" - text -> {s}\n", .{b.text.?.content}); -// } -// std.debug.print("----------\n", .{}); -// } -// try testing.expectEqual(@as(usize, 3), sl.body.len); -// } -// -// test "parse verbatim simples" { -// const allocator = testing.allocator; -// const template = "Texto {% verbatim %}{{ variável }}{% endblock %}{% endverbatim %} Texto"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 3), nodes.len); -// try testing.expectEqualStrings("Texto ", nodes[0].text.?.content); -// try testing.expectEqualStrings("{{ variável }}{% endblock %}", nodes[1].text.?.content); -// try testing.expectEqualStrings(" Texto", nodes[2].text.?.content); -// } -// -// test "parse verbatim aninhado" { -// const allocator = testing.allocator; -// const template = "{% verbatim %}Outer {% verbatim %}Inner{% endverbatim %} Outer{% endverbatim %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Outer {% verbatim %}Inner{% endverbatim %} Outer", nodes[0].text.?.content); -// } -// -// test "parse url simples" { -// const allocator = testing.allocator; -// const template = "Link: {% url 'home' %} Fim"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 3), nodes.len); -// try testing.expect(nodes[1].type == .url); -// const u = nodes[1].url.?; -// try testing.expectEqualStrings("home", u.name); -// try testing.expectEqual(@as(usize, 0), u.args.len); -// } -// -// test "parse url com argumentos" { -// const allocator = testing.allocator; -// const template = "{% url 'post_detail' post.id \"comentario\" %}"; -// const nodes = try parser.parse(allocator, template); -// defer { -// for (nodes) |node| node.deinit(allocator); -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 1), nodes.len); -// try testing.expect(nodes[0].type == .url); -// const u = nodes[0].url.?; -// try testing.expectEqualStrings("post_detail", u.name); -// try testing.expectEqual(@as(usize, 2), u.args.len); -// try testing.expectEqualStrings("post.id", u.args[0]); -// try testing.expectEqualStrings("\"comentario\"", u.args[1]); -// } +test "parse texto simples" { + const allocator = testing.allocator; + const template = "Olá mundo!"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Olá mundo!", nodes[0].text.?.content); +} + +test "parse variável simples" { + const allocator = testing.allocator; + const template = "Olá {{ nome }}!"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Olá ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .variable); + try testing.expectEqualStrings("nome", nodes[1].variable.?.expr); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings("!", nodes[2].text.?.content); +} + +test "parse variável com espaços" { + const allocator = testing.allocator; + const template = "{{ espacos }}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .variable); + try testing.expectEqualStrings("espacos", nodes[0].variable.?.expr); +} + +test "parse tag simples" { + const allocator = testing.allocator; + const template = "Antes {% minha_tag %} Depois"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Antes ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expectEqualStrings("minha_tag", nodes[1].tag.?.name); + try testing.expectEqualStrings("", nodes[1].tag.?.args); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); +} + +test "parse if block básico" { + const allocator = testing.allocator; + const template = "{% if usuario.logado %}Bem-vindo!{% endif %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .if_block); + + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("usuario.logado", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expect(nodes[0].@"if".?.true_body[0].type == .text); + try testing.expectEqualStrings("Bem-vindo!", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 0), ib.false_body.len); +} + +test "parse if block sem else" { + const allocator = testing.allocator; + const template = "{% if cond %}Verdadeiro{% endif %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .if_block); + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("cond", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 0), ib.false_body.len); +} + +test "parse if block com else" { + const allocator = testing.allocator; + const template = "{% if cond %}Verdadeiro{% else %}Falso{% endif %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .if_block); + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("cond", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 1), ib.false_body.len); + try testing.expectEqualStrings("Falso", ib.false_body[0].text.?.content); +} + +test "parse for block sem empty" { + const allocator = testing.allocator; + const template = "{% for item in lista %}Item: {{ item }}{% endfor %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .for_block); + const fb = nodes[0].@"for".?; + try testing.expectEqualStrings("item", fb.loop_var); + try testing.expectEqualStrings("lista", fb.iterable); + try testing.expectEqual(@as(usize, 2), fb.body.len); // <--- corrigido: 2 nós + try testing.expectEqual(@as(usize, 0), fb.empty_body.len); + + try testing.expect(fb.body[0].type == .text); + try testing.expectEqualStrings("Item: ", fb.body[0].text.?.content); + try testing.expect(fb.body[1].type == .variable); + try testing.expectEqualStrings("item", fb.body[1].variable.?.expr); +} + +test "parse for block com empty" { + const allocator = testing.allocator; + const template = "{% for item in lista %}Tem{% empty %}Vazio{% endfor %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .for_block); + const fb = nodes[0].@"for".?; + try testing.expectEqual(@as(usize, 1), fb.body.len); + try testing.expectEqualStrings("Tem", fb.body[0].text.?.content); + try testing.expectEqual(@as(usize, 1), fb.empty_body.len); + try testing.expectEqualStrings("Vazio", fb.empty_body[0].text.?.content); +} + +test "parse comment" { + const allocator = testing.allocator; + const template = "Bazinga! {% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Bazinga! ", nodes[0].text.?.content); +} + +test "parse include simples" { + const allocator = testing.allocator; + const template = "Cabeçalho {% include \"header.zdt\" %} Conteúdo {% include \"footer.zdt\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 4), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Cabeçalho ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .include); + try testing.expectEqualStrings("header.zdt", nodes[1].include.?.template_name); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Conteúdo ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .include); + try testing.expectEqualStrings("footer.zdt", nodes[3].include.?.template_name); + +} + +test "parse include sem aspas (erro esperado no futuro, mas por enquanto aceita)" { + const allocator = testing.allocator; + const template = "{% include header.zdt %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .include); + try testing.expectEqualStrings("header.zdt", nodes[0].include.?.template_name); +} + +test "parse with simples" { + const allocator = testing.allocator; + const template = "{% with nome=\"Lucas\" idade=30 %}Olá {{ nome }}, você tem {{ idade }} anos.{% endwith %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .with_block); + const w = nodes[0].with.?; + try testing.expectEqual(@as(usize, 2), w.assignments.len); + try testing.expectEqual(@as(usize, 5), w.body.len); + try testing.expect(w.body[0].type == .text); +} + +test "parse now simples" { + const allocator = testing.allocator; + const template = "Data atual: {% now \"Y-m-d\" %} às {% now \"H:i\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 4), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Data atual: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .now); + try testing.expectEqualStrings("Y-m-d", nodes[1].now.?.format); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" às ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .now); + try testing.expectEqualStrings("H:i", nodes[3].now.?.format); + +} + +test "parse now sem aspas" { + const allocator = testing.allocator; + const template = "{% now Y-m-d %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .now); + try testing.expectEqualStrings("Y-m-d", nodes[0].now.?.format); +} + +test "parse extends" { + const allocator = testing.allocator; + const template = "{% extends \"base.zdt\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .extends); + try testing.expectEqualStrings("base.zdt", nodes[0].extends.?.parent_name); +} + +test "parse block simples" { + const allocator = testing.allocator; + const template = "{% block titulo %}Meu Título{% endblock %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .block); + const b = nodes[0].block.?; + try testing.expectEqualStrings("titulo", b.name); + try testing.expectEqual(@as(usize, 1), b.body.len); + try testing.expectEqualStrings("Meu Título", b.body[0].text.?.content); +} + +test "parse block com super" { + const allocator = testing.allocator; + const template = "{% block conteudo %}{{ block.super }} Conteúdo filho{% endblock %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .block); + const b = nodes[0].block.?; + try testing.expectEqual(@as(usize, 2), b.body.len); + try testing.expect(b.body[0].type == .super); + // try testing.expectEqualStrings("conteudo", b.name); + try testing.expectEqualStrings(" Conteúdo filho", b.body[1].text.?.content); +} + + +test "parse filter block simples" { + const allocator = testing.allocator; + const template = "{% filter upper %}olá mundo{% endfilter %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .filter_block); + const fb = nodes[0].filter_block.?; + try testing.expectEqualStrings("upper", fb.filters); // correto + try testing.expectEqual(@as(usize, 1), fb.body.len); + try testing.expectEqualStrings("olá mundo", fb.body[0].text.?.content); +} + +test "parse filter block com múltiplos filtros" { + const allocator = testing.allocator; + const template = "{% filter upper|escape %}Conteúdo negrito{% endfilter %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .filter_block); + const fb = nodes[0].filter_block.?; + try testing.expectEqualStrings("upper|escape", fb.filters); + try testing.expectEqual(@as(usize, 1), fb.body.len); + try testing.expectEqualStrings("Conteúdo negrito", fb.body[0].text.?.content); +} + +test "parse variable com filtros" { + const allocator = testing.allocator; + const template = "{{ nome|upper|default:\"Visitante\" }}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .variable); + const v = nodes[0].variable.?; + try testing.expectEqualStrings("nome", v.expr); + try testing.expectEqual(@as(usize, 2), v.filters.len); + try testing.expectEqualStrings("upper", v.filters[0].name); + try testing.expect(v.filters[0].arg == null); + try testing.expectEqualStrings("default", v.filters[1].name); + try testing.expectEqualStrings("Visitante", v.filters[1].arg.?); +} + +test "parse autoescape on" { + const allocator = testing.allocator; + const template = "{% autoescape on %}Texto negrito{% endautoescape %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .autoescape); + const ae = nodes[0].autoescape.?; + try testing.expect(ae.enabled == true); + try testing.expectEqual(@as(usize, 1), ae.body.len); + try testing.expectEqualStrings("Texto negrito", ae.body[0].text.?.content); +} + +test "parse autoescape off" { + const allocator = testing.allocator; + const template = "{% autoescape off %}Texto negrito{% endautoescape %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .autoescape); + const ae = nodes[0].autoescape.?; + try testing.expect(ae.enabled == false); + try testing.expectEqual(@as(usize, 1), ae.body.len); +} + +test "parse spaceless simples" { + const allocator = testing.allocator; + const template = "{% spaceless %}Texto com espaços
{% endspaceless %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .spaceless); + const sl = nodes[0].spaceless.?; + try testing.expectEqual(@as(usize, 1), sl.body.len); + try testing.expectEqualStrings("Texto com espaços
", sl.body[0].text.?.content); +} + +test "parse spaceless aninhado" { + const allocator = testing.allocator; + const template = "{% spaceless %}Outer {% spaceless %}Inner{% endspaceless %} Outer{% endspaceless %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .spaceless); + const sl = nodes[0].spaceless.?; + for (sl.body) |b| { + std.debug.print("type: {s}\n", .{@tagName(b.type)}); + if (b.type == .spaceless) { + std.debug.print(" - tag -> spaceless\n", .{}); + } + if(b.type == .text) { + std.debug.print(" - text -> {s}\n", .{b.text.?.content}); + } + std.debug.print("----------\n", .{}); + } + try testing.expectEqual(@as(usize, 3), sl.body.len); +} + +test "parse verbatim simples" { + const allocator = testing.allocator; + const template = "Texto {% verbatim %}{{ variável }}{% endblock %}{% endverbatim %} Texto"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expectEqualStrings("Texto ", nodes[0].text.?.content); + try testing.expectEqualStrings("{{ variável }}{% endblock %}", nodes[1].text.?.content); + try testing.expectEqualStrings(" Texto", nodes[2].text.?.content); +} + +test "parse verbatim aninhado" { + const allocator = testing.allocator; + const template = "{% verbatim %}Outer {% verbatim %}Inner{% endverbatim %} Outer{% endverbatim %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Outer {% verbatim %}Inner{% endverbatim %} Outer", nodes[0].text.?.content); +} + +test "parse url simples" { + const allocator = testing.allocator; + const template = "Link: {% url 'home' %} Fim"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expect(nodes[1].type == .url); + const u = nodes[1].url.?; + try testing.expectEqualStrings("home", u.name); + try testing.expectEqual(@as(usize, 0), u.args.len); +} + +test "parse url com argumentos" { + const allocator = testing.allocator; + const template = "{% url 'post_detail' post.id \"comentario\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .url); + const u = nodes[0].url.?; + try testing.expectEqualStrings("post_detail", u.name); + try testing.expectEqual(@as(usize, 2), u.args.len); + try testing.expectEqualStrings("post.id", u.args[0]); + try testing.expectEqualStrings("\"comentario\"", u.args[1]); +} + +test "parse cycle simples" { + const allocator = testing.allocator; + const template = "{% cycle 'row1' 'row2' %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .cycle); + const c = nodes[0].cycle.?; + try testing.expectEqual(@as(usize, 2), c.values.len); + try testing.expectEqualStrings("row1", c.values[0]); + try testing.expectEqualStrings("row2", c.values[1]); +} + +test "parse cycle com valores sem aspas" { + const allocator = testing.allocator; + const template = "{% cycle row1 row2 row3 %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .cycle); + const c = nodes[0].cycle.?; + try testing.expectEqual(@as(usize, 3), c.values.len); + try testing.expectEqualStrings("row1", c.values[0]); + try testing.expectEqualStrings("row2", c.values[1]); + try testing.expectEqualStrings("row3", c.values[2]); +} + +test "parse firstof simples" { + const allocator = testing.allocator; + const template = "Valor: {% firstof var1 var2 var3 %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 2), nodes.len); // corrigido + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Valor: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .firstof); + const fo = nodes[1].firstof.?; + try testing.expectEqual(@as(usize, 3), fo.values.len); + try testing.expectEqualStrings("var1", fo.values[0]); + try testing.expectEqualStrings("var2", fo.values[1]); + try testing.expectEqualStrings("var3", fo.values[2]); +} + +test "parse firstof com fallback" { + const allocator = testing.allocator; + const template = "{% firstof var1 var2 \"Nenhum valor\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .firstof); + const fo = nodes[0].firstof.?; + try testing.expectEqual(@as(usize, 3), fo.values.len); + try testing.expectEqualStrings("var1", fo.values[0]); + try testing.expectEqualStrings("var2", fo.values[1]); + try testing.expectEqualStrings("Nenhum valor", fo.values[2]); +}