zdt-prov/src/parser.zig
2026-01-03 18:46:04 -03:00

1330 lines
49 KiB
Zig

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);
}