update: add extends, block, super, filter_block, autoescape and spaceless

This commit is contained in:
Lucas F. 2026-01-03 18:25:09 -03:00
parent 261c02f59b
commit 0192ad0b64
3 changed files with 822 additions and 375 deletions

View file

@ -9,9 +9,42 @@ pub const NodeType = enum {
include,
with_block,
now,
extends, // <--- novo
block, // <--- novo
super, // <--- novo (para {{ block.super }})
extends,
block,
super,
filter_block,
autoescape,
spaceless,
};
pub const AutoescapeNode = struct {
body: []Node,
raw_open: []const u8,
raw_close: []const u8,
enabled: bool,
};
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 {
@ -50,10 +83,6 @@ pub const TextNode = struct {
content: []const u8,
};
pub const VariableNode = struct {
content: []const u8,
};
pub const TagNode = struct {
name: []const u8,
args: []const u8,
@ -90,35 +119,43 @@ pub const Node = struct {
extends: ?ExtendsNode = null,
block: ?BlockNode = null,
super: bool = false, // para {{ block.super }}
filter_block: ?FilterBlockNode = null,
autoescape: ?AutoescapeNode = null,
spaceless: ?SpacelessNode = 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.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);
// NÃO free ib.raw_open
// NÃO free ib.raw_close
for (ib.true_body) |n| n.deinit(allocator);
const true_copy = ib.true_body;
for (true_copy) |n| n.deinit(allocator);
allocator.free(ib.true_body);
for (ib.false_body) |n| n.deinit(allocator);
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);
for (fb.body) |n| n.deinit(allocator);
const body_copy = fb.body;
for (body_copy) |n| n.deinit(allocator);
allocator.free(fb.body);
for (fb.empty_body) |n| n.deinit(allocator);
const empty_copy = fb.empty_body;
for (empty_copy) |n| n.deinit(allocator);
allocator.free(fb.empty_body);
// raw_open e raw_close são slices originais não free
},
.include => if (self.include) |inc| {
allocator.free(inc.template_name);
},
.with_block => if (self.with) |w| {
for (w.assignments) |a| {
@ -126,22 +163,35 @@ pub const Node = struct {
allocator.free(a.value_expr);
}
allocator.free(w.assignments);
for (w.body) |n| n.deinit(allocator);
const body_copy = w.body;
for (body_copy) |n| n.deinit(allocator);
allocator.free(w.body);
// raw_open e raw_close são slices originais não free
},
.now => if (self.now) |n| {
allocator.free(n.format);
},
.extends => if (self.extends) |e| {
allocator.free(e.parent_name);
},
.block => if (self.block) |b| {
allocator.free(b.name);
for (b.body) |n| n.deinit(allocator);
const body_copy = b.body;
for (body_copy) |n| n.deinit(allocator);
allocator.free(b.body);
// raw_open e raw_close são slices originais não free
},
.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 => {},
}
}
@ -169,16 +219,6 @@ pub const Parser = struct {
while (self.pos < self.template.len and std.ascii.isWhitespace(self.template[self.pos])) : (self.advance(1)) {}
}
// fn parseAssignments(allocator: std.mem.Allocator, args: []const u8) ![]const Assignment {
// var list = std.ArrayList(Assignment){};
// defer list.deinit(allocator);
//
// // Implementação básica você tem uma melhor, use ela
// // Por enquanto, para passar teste
// _ = args;
// return try list.toOwnedSlice(allocator);
// }
fn parseAssignments(
self: *Parser,
allocator: std.mem.Allocator,
@ -242,6 +282,185 @@ pub const Parser = struct {
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 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, // 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);
@ -256,9 +475,9 @@ pub const Parser = struct {
if (try self.parseVariable(allocator)) |node| {
if (node.variable) |v| {
if (std.mem.eql(u8, v.content, "block.super")) {
if (std.mem.eql(u8, v.expr, "block.super")) {
try body.append(allocator, Node{ .type = .super, .super = true });
allocator.free(v.content);
allocator.free(v.expr);
continue;
}
}
@ -321,7 +540,7 @@ pub const Parser = struct {
}
if (try self.parseVariable(allocator)) |node| {
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.content});
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr});
try body.append(allocator, node);
continue;
}
@ -448,18 +667,20 @@ pub const Parser = struct {
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;
std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{{{\n", .{});
} else return null;
self.advance(2);
self.skipWhitespace();
const content_start = self.pos;
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", .{});
}
}
@ -469,16 +690,45 @@ pub const Parser = struct {
return error.UnclosedVariable;
}
const raw_content = self.template[content_start..self.pos];
const content = std.mem.trim(u8, raw_content, " \t\r\n");
std.debug.print("2.3 - meu content é \'{s}\'\n", .{content});
const full_expr = std.mem.trim(u8, self.template[expr_start..self.pos], " \t\r\n");
const duped = try allocator.dupe(u8, content);
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 = .{ .content = duped },
.variable = .{
.expr = duped_expr,
.filters = try filters.toOwnedSlice(allocator),
},
};
}
@ -547,7 +797,7 @@ pub const Parser = struct {
}
if (try self.parseVariable(allocator)) |node| {
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.content});
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr});
try current_body.append(allocator, node);
continue;
}
@ -628,7 +878,7 @@ pub const Parser = struct {
}
if (try self.parseVariable(allocator)) |node| {
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.content});
std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr});
try current_body.append(allocator, node);
continue;
}
@ -706,6 +956,42 @@ pub const Parser = struct {
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", .{});
@ -715,6 +1001,24 @@ pub const Parser = struct {
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\"");
@ -868,7 +1172,8 @@ pub const Parser = struct {
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.?.content});
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;