update: add extends, block, super, filter_block, autoescape and spaceless
This commit is contained in:
parent
261c02f59b
commit
0192ad0b64
3 changed files with 822 additions and 375 deletions
407
src/parser.zig
407
src/parser.zig
|
|
@ -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ê já tem uma melhor, use ela
|
||||
// // Por enquanto, só 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, // 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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue