diff --git a/src/parser.zig b/src/parser.zig index d77a292..e77ecd7 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -23,18 +23,61 @@ pub const ParserError = error{ UnexpectedToken, }; -pub const NodeType = enum { +pub const TagNodeBody = union(enum) { + autoescape: AutoescapeNode, + block: BlockNode, + csrf_token: bool, + cycle: CycleNode, + debug: bool, + extends: ExtendsNode, + filter_block: FilterBlockNode, + firstof: FirstOfNode, + @"for": ForNode, + @"if": IfNode, + include: IncludeNode, + load: LoadNode, + lorem: LoremNode, + now: NowNode, + partial: PartialNode, + partialdef: PartialDefNode, + querystring: QueryStringNode, + regroup: RegroupNode, + resetcycle: ResetCycleNode, + spaceless: SpacelessNode, + super: bool, + templatetag: TemplateTagNode, + url: UrlNode, + verbatim: VerbatimNode, + widthratio: WidthRatioNode, + with: WithNode, + initial: bool, +}; + +pub const TagKind = enum { autoescape, block, csrf_token, + comment, cycle, debug, + else_block, + empty, + endautoescape, + endblock, + endfilter, + endfor_block, + endif_block, + endpartialdef, + endspaceless, + endverbatim, + endwith_block, extends, filter_block, firstof, for_block, if_block, include, + initial, load, lorem, now, @@ -45,15 +88,61 @@ pub const NodeType = enum { resetcycle, spaceless, super, - tag, templatetag, - text, url, - variable, + verbatim, widthratio, with_block, }; +pub const NodeType = enum { + tag, + text, + variable, +}; + +fn getTagKindByName(name: []const u8) TagKind { + if (std.mem.eql(u8, name, "autoescape")) return .autoescape; + if (std.mem.eql(u8, name, "block")) return .block; + if (std.mem.eql(u8, name, "csrf_token")) return .csrf_token; + if (std.mem.eql(u8, name, "comment")) return .comment; + if (std.mem.eql(u8, name, "cycle")) return .cycle; + if (std.mem.eql(u8, name, "debug")) return .debug; + if (std.mem.eql(u8, name, "else")) return .else_block; + if (std.mem.eql(u8, name, "empty")) return .empty; + if (std.mem.eql(u8, name, "endautoescape")) return .endautoescape; + if (std.mem.eql(u8, name, "endblock")) return .endblock; + if (std.mem.eql(u8, name, "endfilter")) return .endfilter; + if (std.mem.eql(u8, name, "endfor")) return .endfor_block; + if (std.mem.eql(u8, name, "endif")) return .endif_block; + if (std.mem.eql(u8, name, "endpartialdef")) return .endpartialdef; + if (std.mem.eql(u8, name, "endspaceless")) return .endspaceless; + if (std.mem.eql(u8, name, "endverbatim")) return .endverbatim; + if (std.mem.eql(u8, name, "endwith")) return .endwith_block; + if (std.mem.eql(u8, name, "extends")) return .extends; + if (std.mem.eql(u8, name, "filter")) return .filter_block; + if (std.mem.eql(u8, name, "firstof")) return .firstof; + if (std.mem.eql(u8, name, "for")) return .for_block; + if (std.mem.eql(u8, name, "if")) return .if_block; + if (std.mem.eql(u8, name, "include")) return .include; + if (std.mem.eql(u8, name, "load")) return .load; + if (std.mem.eql(u8, name, "lorem")) return .lorem; + if (std.mem.eql(u8, name, "now")) return .now; + if (std.mem.eql(u8, name, "partial")) return .partial; + if (std.mem.eql(u8, name, "partialdef")) return .partialdef; + if (std.mem.eql(u8, name, "querystring")) return .querystring; + if (std.mem.eql(u8, name, "regroup")) return .regroup; + if (std.mem.eql(u8, name, "resetcycle")) return .resetcycle; + if (std.mem.eql(u8, name, "spaceless")) return .spaceless; + if (std.mem.eql(u8, name, "super")) return .super; + if (std.mem.eql(u8, name, "templatetag")) return .templatetag; + if (std.mem.eql(u8, name, "url")) return .url; + if (std.mem.eql(u8, name, "verbatim")) return .verbatim; + if (std.mem.eql(u8, name, "widthratio")) return .widthratio; + if (std.mem.eql(u8, name, "with")) return .with_block; + return .initial; +} + pub const TemplateTagNode = struct { kind: enum { openblock, closeblock, openvariable, closevariable, openbrace, closebrace, opencomment, closecomment }, @@ -71,24 +160,83 @@ pub const TemplateTagNode = struct { } }; -pub const WidthRatioNode = struct { - value: []const u8, - max_value: []const u8, - divisor: ?[]const u8 = null, // opcional +pub const Assignment = struct { + key: []const u8, + value_expr: []const u8, + is_literal: bool, }; -pub const ResetCycleNode = struct { - cycle_name: ?[]const u8 = null, // null = reseta todos, ou nome específico +pub const AutoescapeNode = struct { + body: []Node, + raw_open: []const u8, + raw_close: []const u8, + enabled: bool, }; -pub const RegroupNode = struct { - source: []const u8, - by: []const u8, - as_var: []const u8, +pub const BlockNode = struct { + name: []const u8, + body: []Node, + raw_open: []const u8, + raw_close: []const u8, }; -pub const QueryStringNode = struct { - modifications: []const []const u8, // array de "key=value" ou "key=None" +pub const CycleNode = struct { + values: []const []const u8, +}; + +pub const ExtendsNode = struct { + parent_name: []const u8, +}; + +pub const Filter = struct { + name: []const u8, + arg: ?[]const u8 = null, +}; + +pub const FilterBlockNode = struct { + filters: []const u8, // "upper|escape|truncatewords:30" + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const FirstOfNode = struct { + values: []const []const u8, +}; + +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 IfNode = struct { + condition: []const u8, + true_body: []Node, + false_body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const IncludeNode = struct { + template_path: []const u8, +}; + +pub const LoadNode = struct { + libraries: []const []const u8, +}; + +pub const LoremNode = struct { + count: ?[]const u8 = null, // "3" ou null + method: ?[]const u8 = null, // "p" ou "w" ou null + format: ?[]const u8 = null, // "html" ou null +}; + +pub const NowNode = struct { + format: []const u8, }; pub const PartialDefNode = struct { @@ -102,34 +250,18 @@ pub const PartialNode = struct { name: []const u8, }; -pub const LoremNode = struct { - count: ?[]const u8 = null, // "3" ou null - method: ?[]const u8 = null, // "p" ou "w" ou null - format: ?[]const u8 = null, // "html" ou null +pub const QueryStringNode = struct { + modifications: []const []const u8, // array de "key=value" ou "key=None" }; -pub const LoadNode = struct { - libraries: []const []const u8, +pub const RegroupNode = struct { + source: []const u8, + by: []const u8, + as_var: []const u8, }; -pub const AutoescapeNode = struct { - body: []Node, - raw_open: []const u8, - raw_close: []const u8, - 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, +pub const ResetCycleNode = struct { + cycle_name: ?[]const u8 = null, // null = reseta todos, ou nome específico }; pub const SpacelessNode = struct { @@ -138,16 +270,27 @@ pub const SpacelessNode = struct { 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 TagNode = struct { + kind: TagKind, + args: []const u8, + raw: []const u8, + body: TagNodeBody, }; -pub const Filter = struct { +pub const TextNode = struct { + content: []const u8, +}; + +pub const UrlNode = struct { name: []const u8, - arg: ?[]const u8 = null, // null ou string com argumento + args: []const []const u8, +}; + +pub const VerbatimNode = struct { + name: ?[]const u8, + content: []const u8, + raw_open: []const u8, + raw_close: []const u8, }; pub const VariableNode = struct { @@ -155,25 +298,10 @@ pub const VariableNode = struct { 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 WidthRatioNode = struct { + value: []const u8, + max_value: []const u8, + divisor: ?[]const u8 = null, // opcional }; pub const WithNode = struct { @@ -183,203 +311,98 @@ pub const WithNode = struct { 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, - filter_block: ?FilterBlockNode = null, - autoescape: ?AutoescapeNode = null, - spaceless: ?SpacelessNode = null, - url: ?UrlNode = null, - cycle: ?CycleNode = null, - firstof: ?FirstOfNode = null, - load: ?LoadNode = null, - lorem: ?LoremNode = null, - partialdef: ?PartialDefNode = null, - partial: ?PartialNode = null, - querystring: ?QueryStringNode = null, - regroup: ?RegroupNode = null, - resetcycle: ?ResetCycleNode = null, - widthratio: ?WidthRatioNode = null, - templatetag: ?TemplateTagNode = null, - super: bool = false, - csrf_token: bool = false, - debug: bool = false, pub fn deinit(self: Node, allocator: std.mem.Allocator) void { switch (self.type) { - .text => if (self.text) |t| allocator.free(t.content), + // .text => if (self.text) |t| allocator.free(t.content), + .text => {}, .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); - allocator.free(t.raw); - }, - .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); - allocator.free(ib.raw_open); - allocator.free(ib.raw_close); - }, - .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); - allocator.free(fb.raw_open); - allocator.free(fb.raw_close); - }, - .include => if (self.include) |inc| allocator.free(inc.template_name), - .with_block => if (self.with) |w| { - for (w.assignments) |a| { - allocator.free(a.key); - allocator.free(a.value_expr); + .tag => { + if (self.tag) |t| { + switch (t.body) { + .autoescape => { + allocator.free(t.body.autoescape.body); + }, + .block => { + const body_copy = t.body.block.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + }, + .cycle => { + allocator.free(t.body.cycle.values); + }, + .filter_block => { + const body_copy = t.body.filter_block.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + }, + .firstof => { + allocator.free(t.body.firstof.values); + }, + .@"for" => { + const body_copy = t.body.@"for".body; + for (body_copy) |n| n.deinit(allocator); + const empty_body_copy = t.body.@"for".empty_body; + for (empty_body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + allocator.free(empty_body_copy); + }, + .@"if" => { + const true_copy = t.body.@"if".true_body; + for (true_copy) |n| n.deinit(allocator); + const false_copy = t.body.@"if".false_body; + for (false_copy) |n| n.deinit(allocator); + allocator.free(true_copy); + allocator.free(false_copy); + }, + .load => { + allocator.free(t.body.load.libraries); + }, + .partialdef => { + const body_copy = t.body.partialdef.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + }, + .querystring => { + allocator.free(t.body.querystring.modifications); + }, + .spaceless => { + const body_copy = t.body.spaceless.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + }, + .url => { + allocator.free(t.body.url.args); + }, + .with => { + const body_copy = t.body.with.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(body_copy); + allocator.free(t.body.with.assignments); + }, + else => {}, + } } - allocator.free(w.assignments); - const body_copy = w.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(w.body); - allocator.free(w.raw_open); - allocator.free(w.raw_close); }, - .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); - const body_copy = b.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(b.body); - allocator.free(b.raw_open); - allocator.free(b.raw_close); - }, - .super => {}, - .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); - allocator.free(ae.raw_open); - allocator.free(ae.raw_close); - }, - .spaceless => if (self.spaceless) |sl| { - const body_copy = sl.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(sl.body); - allocator.free(sl.raw_open); - allocator.free(sl.raw_close); - }, - .url => if (self.url) |u| { - allocator.free(u.name); - 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); - }, - .load => if (self.load) |l| { - for (l.libraries) |lib| allocator.free(lib); - allocator.free(l.libraries); - }, - .csrf_token => {}, - .lorem => if (self.lorem) |l| { - if (l.count) |c| allocator.free(c); - if (l.method) |m| allocator.free(m); - if (l.format) |f| allocator.free(f); - }, - .debug => {}, - .partialdef => if (self.partialdef) |pd| { - allocator.free(pd.name); - allocator.free(pd.raw_open); - allocator.free(pd.raw_close); - for (pd.body) |n| n.deinit(allocator); - allocator.free(pd.body); - }, - .partial => if (self.partial) |p| { - allocator.free(p.name); - }, - .querystring => if (self.querystring) |qs| { - for (qs.modifications) |m| allocator.free(m); - allocator.free(qs.modifications); - }, - .regroup => if (self.regroup) |r| { - allocator.free(r.source); - allocator.free(r.by); - allocator.free(r.as_var); - }, - .resetcycle => if (self.resetcycle) |rc| { - if (rc.cycle_name) |name| allocator.free(name); - }, - .widthratio => if (self.widthratio) |wr| { - allocator.free(wr.value); - allocator.free(wr.max_value); - if (wr.divisor) |d| allocator.free(d); - }, - .templatetag => {}, + // .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); + // allocator.free(w.raw_open); + // allocator.free(w.raw_close); + // }, } } @@ -411,313 +434,311 @@ pub const Node = struct { }, }; }, - - .if_block => { - const true_body_copy = try allocator.alloc(Node, self.@"if".?.true_body.len); - errdefer allocator.free(true_body_copy); - - for (self.@"if".?.true_body, 0..) |child, j| { - true_body_copy[j] = try child.clone(allocator); - } - - const false_body_copy = try allocator.alloc(Node, self.@"if".?.false_body.len); - errdefer allocator.free(false_body_copy); - - for (self.@"if".?.false_body, 0..) |child, j| { - false_body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .if_block, - .@"if" = .{ - .condition = try allocator.dupe(u8, self.@"if".?.condition), - .true_body = true_body_copy, - .false_body = false_body_copy, - .raw_open = try allocator.dupe(u8, self.@"if".?.raw_open), - .raw_close = try allocator.dupe(u8, self.@"if".?.raw_close), - }, - }; - }, - - .for_block => { - const body_copy = try allocator.alloc(Node, self.@"for".?.body.len); - errdefer allocator.free(body_copy); - - for (self.@"for".?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - const empty_body_copy = try allocator.alloc(Node, self.@"for".?.empty_body.len); - errdefer allocator.free(empty_body_copy); - - for (self.@"for".?.empty_body, 0..) |child, j| { - empty_body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .for_block, - .@"for" = .{ - .loop_var = try allocator.dupe(u8, self.@"for".?.loop_var), - .iterable = try allocator.dupe(u8, self.@"for".?.iterable), - .body = body_copy, - .empty_body = empty_body_copy, - .raw_open = try allocator.dupe(u8, self.@"for".?.raw_open), - .raw_close = try allocator.dupe(u8, self.@"for".?.raw_close), - }, - }; - }, - - .block => { - const body_copy = try allocator.alloc(Node, self.block.?.body.len); - errdefer allocator.free(body_copy); - - for (self.block.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .block, - .block = .{ - .name = try allocator.dupe(u8, self.block.?.name), - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.block.?.raw_open), - .raw_close = try allocator.dupe(u8, self.block.?.raw_close), - }, - }; - }, - - .extends => { - return Node{ - .type = .extends, - .extends = .{ - .parent_name = try allocator.dupe(u8, self.extends.?.parent_name), - }, - }; - }, - - .super => { - return Node{ - .type = .super, - }; - }, .tag => { return Node{ .type = .tag, .tag = .{ - .name = try allocator.dupe(u8, self.tag.?.name), .args = try allocator.dupe(u8, self.tag.?.args), .raw = try allocator.dupe(u8, self.tag.?.raw), + .body = try allocator.dupe(u8, self.tag.?.body), }, }; }, - .include => { - return Node{ - .type = .include, - .include = .{ - .template_name = try allocator.dupe(u8, self.include.?.template_name), - }, - }; - }, - .with_block => { - const body_copy = try allocator.alloc(Node, self.with.?.body.len); - errdefer allocator.free(body_copy); + .debug => return Node{ .type = .tag, .tag = .{ .debug = true } }, + // .firstof => { + // const values_copy = try allocator.alloc([]const u8, self.firstof.?.values.len); + // errdefer allocator.free(values_copy); + // + // for (self.firstof.?.values, 0..) |f, i| { + // values_copy[i] = try allocator.dupe(u8, f); + // } + // + // return Node{ .type = .firstof, .firstof = .{ .values = values_copy } }; + // }, - for (self.with.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - const assignments_copy = try allocator.alloc(Assignment, self.with.?.assignments.len); - errdefer allocator.free(assignments_copy); - - for (self.with.?.assignments, 0..) |f, i| { - assignments_copy[i] = .{ - .key = try allocator.dupe(u8, f.key), - .value_expr = try allocator.dupe(u8, f.value_expr), - .is_literal = f.is_literal, - }; - } - - return Node{ - .type = .with_block, - .with = .{ - .assignments = assignments_copy, - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.with.?.raw_open), - .raw_close = try allocator.dupe(u8, self.with.?.raw_close), - }, - }; - }, - .now => { - return Node{ .type = .now, .now = .{ .format = try allocator.dupe(u8, self.now.?.format) } }; - }, - .filter_block => { - const body_copy = try allocator.alloc(Node, self.filter_block.?.body.len); - errdefer allocator.free(body_copy); - - for (self.filter_block.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .filter_block, - .filter_block = .{ - .filters = try allocator.dupe(u8, self.filter_block.?.filters), - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.filter_block.?.raw_open), - .raw_close = try allocator.dupe(u8, self.filter_block.?.raw_close), - }, - }; - }, - .autoescape => { - const body_copy = try allocator.alloc(Node, self.autoescape.?.body.len); - errdefer allocator.free(body_copy); - - for (self.autoescape.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .autoescape, - .autoescape = .{ - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.autoescape.?.raw_open), - .raw_close = try allocator.dupe(u8, self.autoescape.?.raw_close), - .enabled = self.autoescape.?.enabled, - }, - }; - }, - .spaceless => { - const body_copy = try allocator.alloc(Node, self.spaceless.?.body.len); - errdefer allocator.free(body_copy); - - for (self.spaceless.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .spaceless, - .spaceless = .{ - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.spaceless.?.raw_open), - .raw_close = try allocator.dupe(u8, self.spaceless.?.raw_close), - }, - }; - }, - .url => { - const args_copy = try allocator.alloc([]const u8, self.url.?.args.len); - errdefer allocator.free(args_copy); - - for (self.url.?.args, 0..) |f, i| { - args_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ .type = .url, .url = .{ - .name = try allocator.dupe(u8, self.url.?.name), - .args = args_copy, - } }; - }, - .cycle => { - const values_copy = try allocator.alloc([]const u8, self.cycle.?.values.len); - errdefer allocator.free(values_copy); - - for (self.cycle.?.values, 0..) |f, i| { - values_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ .type = .cycle, .cycle = .{ .values = values_copy } }; - }, - .firstof => { - const values_copy = try allocator.alloc([]const u8, self.firstof.?.values.len); - errdefer allocator.free(values_copy); - - for (self.firstof.?.values, 0..) |f, i| { - values_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ .type = .firstof, .firstof = .{ .values = values_copy } }; - }, - .load => { - const libraries_copy = try allocator.alloc([]const u8, self.load.?.libraries.len); - errdefer allocator.free(libraries_copy); - - for (self.load.?.libraries, 0..) |f, i| { - libraries_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ .type = .load, .load = .{ .libraries = libraries_copy } }; - }, - .csrf_token => { - return Node{ - .type = .csrf_token, - }; - }, - .lorem => { - return Node{ - .type = .lorem, - .lorem = .{ - .count = if (self.lorem.?.count) |c| try allocator.dupe(u8, c) else null, - .method = if (self.lorem.?.method) |m| try allocator.dupe(u8, m) else null, - .format = if (self.lorem.?.format) |f| try allocator.dupe(u8, f) else null, - }, - }; - }, - .debug => { - return Node{ .type = .debug }; - }, - .partialdef => { - const body_copy = try allocator.alloc(Node, self.partialdef.?.body.len); - errdefer allocator.free(body_copy); - - for (self.partialdef.?.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ .type = .partialdef, .partialdef = .{ - .name = try allocator.dupe(u8, self.partialdef.?.name), - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.partialdef.?.raw_open), - .raw_close = try allocator.dupe(u8, self.partialdef.?.raw_close), - } }; - }, - .partial => { - return Node{ .type = .partial, .partial = .{ - .name = try allocator.dupe(u8, self.partial.?.name), - } }; - }, - .querystring => { - const modifications = try allocator.alloc([]const u8, self.querystring.?.modifications.len); - errdefer allocator.free(modifications); - - for (self.querystring.?.modifications, 0..) |f, i| { - modifications[i] = try allocator.dupe(u8, f); - } - - return Node{ .type = .querystring, .querystring = .{ - .modifications = modifications, - } }; - }, - .regroup => { - return Node{ .type = .regroup, .regroup = .{ - .source = try allocator.dupe(u8, self.regroup.?.source), - .by = try allocator.dupe(u8, self.regroup.?.by), - .as_var = try allocator.dupe(u8, self.regroup.?.as_var), - } }; - }, - .resetcycle => { - return Node{ .type = .resetcycle, .resetcycle = .{ - .cycle_name = if (self.resetcycle.?.cycle_name) |n| try allocator.dupe(u8, n) else null, - } }; - }, - .widthratio => { - return Node{ .type = .widthratio, .widthratio = .{ - .value = try allocator.dupe(u8, self.widthratio.?.value), - .max_value = try allocator.dupe(u8, self.widthratio.?.max_value), - .divisor = if (self.widthratio.?.divisor) |d| try allocator.dupe(u8, d) else null, - } }; - }, - .templatetag => { - return Node{ .type = .templatetag, .templatetag = .{ .kind = self.templatetag.?.kind } }; - }, + // .if_block => { + // const true_body_copy = try allocator.alloc(Node, self.@"if".?.true_body.len); + // errdefer allocator.free(true_body_copy); + // + // for (self.@"if".?.true_body, 0..) |child, j| { + // true_body_copy[j] = try child.clone(allocator); + // } + // + // const false_body_copy = try allocator.alloc(Node, self.@"if".?.false_body.len); + // errdefer allocator.free(false_body_copy); + // + // for (self.@"if".?.false_body, 0..) |child, j| { + // false_body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .if_block, + // .@"if" = .{ + // .condition = try allocator.dupe(u8, self.@"if".?.condition), + // .true_body = true_body_copy, + // .false_body = false_body_copy, + // .raw_open = try allocator.dupe(u8, self.@"if".?.raw_open), + // .raw_close = try allocator.dupe(u8, self.@"if".?.raw_close), + // }, + // }; + // }, + // + // .for_block => { + // const body_copy = try allocator.alloc(Node, self.@"for".?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.@"for".?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // const empty_body_copy = try allocator.alloc(Node, self.@"for".?.empty_body.len); + // errdefer allocator.free(empty_body_copy); + // + // for (self.@"for".?.empty_body, 0..) |child, j| { + // empty_body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .for_block, + // .@"for" = .{ + // .loop_var = try allocator.dupe(u8, self.@"for".?.loop_var), + // .iterable = try allocator.dupe(u8, self.@"for".?.iterable), + // .body = body_copy, + // .empty_body = empty_body_copy, + // .raw_open = try allocator.dupe(u8, self.@"for".?.raw_open), + // .raw_close = try allocator.dupe(u8, self.@"for".?.raw_close), + // }, + // }; + // }, + // + // .block => { + // const body_copy = try allocator.alloc(Node, self.block.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.block.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .block, + // .block = .{ + // .name = try allocator.dupe(u8, self.block.?.name), + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.block.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.block.?.raw_close), + // }, + // }; + // }, + // + // .extends => { + // return Node{ + // .type = .extends, + // .extends = .{ + // .parent_name = try allocator.dupe(u8, self.extends.?.parent_name), + // }, + // }; + // }, + // + // .super => { + // return Node{ + // .type = .super, + // }; + // }, + // .include => { + // return Node{ + // .type = .include, + // .include = .{ + // .template_name = try allocator.dupe(u8, self.include.?.template_name), + // }, + // }; + // }, + // .with_block => { + // const body_copy = try allocator.alloc(Node, self.with.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.with.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // const assignments_copy = try allocator.alloc(Assignment, self.with.?.assignments.len); + // errdefer allocator.free(assignments_copy); + // + // for (self.with.?.assignments, 0..) |f, i| { + // assignments_copy[i] = .{ + // .key = try allocator.dupe(u8, f.key), + // .value_expr = try allocator.dupe(u8, f.value_expr), + // .is_literal = f.is_literal, + // }; + // } + // + // return Node{ + // .type = .with_block, + // .with = .{ + // .assignments = assignments_copy, + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.with.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.with.?.raw_close), + // }, + // }; + // }, + // .now => { + // return Node{ .type = .now, .now = .{ .format = try allocator.dupe(u8, self.now.?.format) } }; + // }, + // .filter_block => { + // const body_copy = try allocator.alloc(Node, self.filter_block.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.filter_block.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .filter_block, + // .filter_block = .{ + // .filters = try allocator.dupe(u8, self.filter_block.?.filters), + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.filter_block.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.filter_block.?.raw_close), + // }, + // }; + // }, + // .autoescape => { + // const body_copy = try allocator.alloc(Node, self.autoescape.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.autoescape.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .autoescape, + // .autoescape = .{ + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.autoescape.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.autoescape.?.raw_close), + // .enabled = self.autoescape.?.enabled, + // }, + // }; + // }, + // .spaceless => { + // const body_copy = try allocator.alloc(Node, self.spaceless.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.spaceless.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ + // .type = .spaceless, + // .spaceless = .{ + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.spaceless.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.spaceless.?.raw_close), + // }, + // }; + // }, + // .url => { + // const args_copy = try allocator.alloc([]const u8, self.url.?.args.len); + // errdefer allocator.free(args_copy); + // + // for (self.url.?.args, 0..) |f, i| { + // args_copy[i] = try allocator.dupe(u8, f); + // } + // + // return Node{ .type = .url, .url = .{ + // .name = try allocator.dupe(u8, self.url.?.name), + // .args = args_copy, + // } }; + // }, + // .cycle => { + // const values_copy = try allocator.alloc([]const u8, self.cycle.?.values.len); + // errdefer allocator.free(values_copy); + // + // for (self.cycle.?.values, 0..) |f, i| { + // values_copy[i] = try allocator.dupe(u8, f); + // } + // + // return Node{ .type = .cycle, .cycle = .{ .values = values_copy } }; + // }, + // .load => { + // const libraries_copy = try allocator.alloc([]const u8, self.load.?.libraries.len); + // errdefer allocator.free(libraries_copy); + // + // for (self.load.?.libraries, 0..) |f, i| { + // libraries_copy[i] = try allocator.dupe(u8, f); + // } + // + // return Node{ .type = .load, .load = .{ .libraries = libraries_copy } }; + // }, + // .csrf_token => { + // return Node{ + // .type = .csrf_token, + // }; + // }, + // .lorem => { + // return Node{ + // .type = .lorem, + // .lorem = .{ + // .count = if (self.lorem.?.count) |c| try allocator.dupe(u8, c) else null, + // .method = if (self.lorem.?.method) |m| try allocator.dupe(u8, m) else null, + // .format = if (self.lorem.?.format) |f| try allocator.dupe(u8, f) else null, + // }, + // }; + // }, + // .partialdef => { + // const body_copy = try allocator.alloc(Node, self.partialdef.?.body.len); + // errdefer allocator.free(body_copy); + // + // for (self.partialdef.?.body, 0..) |child, j| { + // body_copy[j] = try child.clone(allocator); + // } + // + // return Node{ .type = .partialdef, .partialdef = .{ + // .name = try allocator.dupe(u8, self.partialdef.?.name), + // .body = body_copy, + // .raw_open = try allocator.dupe(u8, self.partialdef.?.raw_open), + // .raw_close = try allocator.dupe(u8, self.partialdef.?.raw_close), + // } }; + // }, + // .partial => { + // return Node{ .type = .partial, .partial = .{ + // .name = try allocator.dupe(u8, self.partial.?.name), + // } }; + // }, + // .querystring => { + // const modifications = try allocator.alloc([]const u8, self.querystring.?.modifications.len); + // errdefer allocator.free(modifications); + // + // for (self.querystring.?.modifications, 0..) |f, i| { + // modifications[i] = try allocator.dupe(u8, f); + // } + // + // return Node{ .type = .querystring, .querystring = .{ + // .modifications = modifications, + // } }; + // }, + // .regroup => { + // return Node{ .type = .regroup, .regroup = .{ + // .source = try allocator.dupe(u8, self.regroup.?.source), + // .by = try allocator.dupe(u8, self.regroup.?.by), + // .as_var = try allocator.dupe(u8, self.regroup.?.as_var), + // } }; + // }, + // .resetcycle => { + // return Node{ .type = .resetcycle, .resetcycle = .{ + // .cycle_name = if (self.resetcycle.?.cycle_name) |n| try allocator.dupe(u8, n) else null, + // } }; + // }, + // .widthratio => { + // return Node{ .type = .widthratio, .widthratio = .{ + // .value = try allocator.dupe(u8, self.widthratio.?.value), + // .max_value = try allocator.dupe(u8, self.widthratio.?.max_value), + // .divisor = if (self.widthratio.?.divisor) |d| try allocator.dupe(u8, d) else null, + // } }; + // }, + // .templatetag => { + // return Node{ .type = .templatetag, .templatetag = .{ .kind = self.templatetag.?.kind } }; + // }, } } }; @@ -798,8 +819,10 @@ pub const Parser = struct { } try list.append(allocator, .{ - .key = try allocator.dupe(u8, key), - .value_expr = try allocator.dupe(u8, value), + // .key = try allocator.dupe(u8, key), + // .value_expr = try allocator.dupe(u8, value), + .key = key, + .value_expr = value, .is_literal = in_quote, }); } @@ -807,7 +830,7 @@ pub const Parser = struct { return try list.toOwnedSlice(allocator); } - fn parsePartialDefBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!Node { + fn parsePartialDefBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -825,31 +848,23 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "partialdef")) { + if (tag_node.tag.?.kind == .partialdef) { depth += 1; try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endpartialdef")) { + if (tag_node.tag.?.kind == .endpartialdef) { 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 = .partialdef, - .partialdef = .{ - .name = name, - .body = try body.toOwnedSlice(allocator), - .raw_open = raw_open, - .raw_close = raw_close, - }, - }; + return TagNodeBody{ .partialdef = .{ + .name = name, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + } }; } try body.append(allocator, tag_node); @@ -865,7 +880,12 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseAutoescapeBlock(self: *Parser, allocator: std.mem.Allocator, enabled: bool, raw_open: []const u8) ParserError!Node { + fn parseAutoescapeBlock( + self: *Parser, + allocator: std.mem.Allocator, + enabled: bool, + raw_open: []const u8, + ) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -883,24 +903,18 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "autoescape")) { + if (tag_node.tag.?.kind == .autoescape) { depth += 1; try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endautoescape")) { + if (tag_node.tag.?.kind == .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, + return TagNodeBody{ .autoescape = .{ .enabled = enabled, .body = try body.toOwnedSlice(allocator), @@ -913,8 +927,6 @@ pub const Parser = struct { try body.append(allocator, tag_node); continue; } - - try body.append(allocator, tag_node); } else { self.advance(1); } @@ -923,11 +935,11 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseVerbatim(self: *Parser, allocator: std.mem.Allocator) ParserError!Node { + fn parseVerbatimBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!TagNodeBody { + _ = allocator; 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, "{%")) { @@ -943,19 +955,22 @@ pub const Parser = struct { const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); + const endverbatim_idx = std.mem.indexOf(u8, inner, "endverbatim"); if (std.mem.eql(u8, inner, "verbatim")) { depth += 1; - } else if (std.mem.eql(u8, inner, "endverbatim")) { + } else if (endverbatim_idx != null) { 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]); + const content = self.template[start..content_end]; self.advance(2); // consome %} - return Node{ - .type = .text, - .text = .{ .content = content }, - }; + return TagNodeBody{ .verbatim = .{ + .name = name, + .content = content, + .raw_open = raw_open, + .raw_close = self.template[start..self.pos], + } }; } } @@ -963,13 +978,14 @@ pub const Parser = struct { continue; } } + self.advance(1); } return error.UnclosedVerbatim; } - fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) ParserError!Node { + fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -988,26 +1004,17 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "spaceless")) { + if (tag_node.tag.?.kind == .spaceless) { depth += 1; - // Ignora a tag open aninhada - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + try body.append(allocator, tag_node); continue; } - - if (std.mem.eql(u8, tag_name, "endspaceless")) { + if (tag_node.tag.?.kind == .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, + return TagNodeBody{ .spaceless = .{ .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, @@ -1015,9 +1022,6 @@ pub const Parser = struct { }, }; } - - // depth > 0: endspaceless aninhado — IGNORA a tag - // NÃO adicione ao body continue; } @@ -1030,7 +1034,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) ParserError!Node { + fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1048,35 +1052,25 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "filter")) { + if (tag_node.tag.?.kind == .filter_block) { depth += 1; try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endfilter")) { + if (tag_node.tag.?.kind == .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, + return TagNodeBody{ .filter_block = .{ - .filters = filters_raw, // já duped + .filters = filters_raw, .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } - try body.append(allocator, tag_node); continue; } @@ -1090,7 +1084,12 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseBlockBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!Node { + fn parseBlockBlock( + self: *Parser, + allocator: std.mem.Allocator, + name: []const u8, + raw_open: []const u8, + ) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1105,8 +1104,7 @@ pub const Parser = struct { 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); + try body.append(allocator, Node{ .type = .tag, .tag = .{ .kind = .super, .args = "", .raw = raw_open, .body = .{ .super = true } } }); continue; } } @@ -1115,46 +1113,39 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "block")) { + if (tag_node.tag.?.kind == .block) { depth += 1; try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endblock")) { + if (tag_node.tag.?.kind == .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, - }, - }; + return TagNodeBody{ .block = .{ + .name = name, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + } }; } try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "comment")) { - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - try self.parseComment(); - continue; - } else { - const node_parsed: ?Node = self.parseTagContent(allocator, tag_node) catch null; - if (node_parsed) |n| { - try body.append(allocator, n); + var tag = try self.parseTag(allocator); + if (tag != null) { + if (tag.?.tag.?.kind == .comment) { + try self.parseComment(); + continue; + } else { + if (try self.parseTagContent(allocator, tag.?)) |tn| { + tag.?.tag.?.body = tn; + try body.append(allocator, tag.?); + } continue; } } @@ -1168,7 +1159,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) ParserError!Node { + fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1186,24 +1177,18 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "with")) { + if (tag_node.tag.?.kind == .with_block) { depth += 1; try body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endwith")) { + if (tag_node.tag.?.kind == .endwith_block) { 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 = .with_block, + return TagNodeBody{ .with = .{ .assignments = assignments, .body = try body.toOwnedSlice(allocator), @@ -1271,6 +1256,7 @@ pub const Parser = struct { } fn parseText(self: *Parser, allocator: std.mem.Allocator) ParserError!?Node { + _ = allocator; const start = self.pos; while (self.pos < self.template.len) { @@ -1284,10 +1270,9 @@ pub const Parser = struct { if (self.pos == start) return null; - const content = try allocator.dupe(u8, self.template[start..self.pos]); return Node{ .type = .text, - .text = .{ .content = content }, + .text = .{ .content = self.template[start..self.pos] }, }; } @@ -1329,15 +1314,17 @@ pub const Parser = struct { 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, + // .name = try allocator.dupe(u8, filter_name), + .name = filter_name, + // .arg = if (filter_arg) |a| try allocator.dupe(u8, a) else null, + .arg = if (filter_arg) |a| 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); + // const duped_expr = try allocator.dupe(u8, base_expr); // Inverte os filters (porque usamos lastIndexOf) std.mem.reverse(Filter, filters.items); @@ -1345,13 +1332,15 @@ pub const Parser = struct { return Node{ .type = .variable, .variable = .{ - .expr = duped_expr, + // .expr = duped_expr, + .expr = base_expr, .filters = try filters.toOwnedSlice(allocator), }, }; } fn parseTag(self: *Parser, allocator: std.mem.Allocator) ParserError!?Node { + _ = allocator; if (self.peek(2)) |p| { if (!std.mem.eql(u8, p, "{%")) return null; } else return null; @@ -1378,22 +1367,23 @@ pub const Parser = struct { 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); - self.advance(2); - return Node{ - .type = .tag, - .tag = .{ - .name = name, - .args = args, - .raw = raw_slice, // slice original, sem dupe - }, - }; + const kind: TagKind = getTagKindByName(name_raw); + return Node{ .type = .tag, .tag = .{ + .kind = kind, + .raw = raw_slice, + .args = args_raw, + .body = .{ .initial = true }, + } }; } - fn parseIfBlock(self: *Parser, allocator: std.mem.Allocator, condition: []const u8, raw_open: []const u8) ParserError!Node { + fn parseIfBlock( + self: *Parser, + allocator: std.mem.Allocator, + condition: []const u8, + raw_open: []const u8, + ) ParserError!TagNodeBody { var true_body = std.ArrayList(Node){}; defer true_body.deinit(allocator); var false_body = std.ArrayList(Node){}; @@ -1414,47 +1404,33 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "if")) { + if (tag_node.tag.?.kind == .if_block) { depth += 1; try current_body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endif")) { + if (tag_node.tag.?.kind == .endif_block) { 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) { - 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, - }, - }; + return TagNodeBody{ .@"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) { + if (tag_node.tag.?.kind == .else_block 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 { @@ -1465,7 +1441,13 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseForBlock(self: *Parser, allocator: std.mem.Allocator, loop_var: []const u8, iterable: []const u8, raw_open: []const u8) ParserError!Node { + fn parseForBlock( + self: *Parser, + allocator: std.mem.Allocator, + loop_var: []const u8, + iterable: []const u8, + raw_open: []const u8, + ) ParserError!TagNodeBody { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var empty_body = std.ArrayList(Node){}; @@ -1486,45 +1468,33 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - const tag_name = tag_node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "for")) { + if (tag_node.tag.?.kind == .for_block) { depth += 1; try current_body.append(allocator, tag_node); continue; } - if (std.mem.eql(u8, tag_name, "endfor")) { + if (tag_node.tag.?.kind == .endfor_block) { 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); - - 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, - }, - }; + return TagNodeBody{ .@"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) { + if (tag_node.tag.?.kind == .empty and depth == 1) { current_body = &empty_body; - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); continue; } @@ -1537,594 +1507,464 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseTagContent(self: *Parser, allocator: std.mem.Allocator, tag_node: Node) ParserError!?Node { - const tag_name = tag_node.tag.?.name; - if (std.mem.eql(u8, tag_name, "autoescape")) { - const args = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); - const raw_open = tag_node.tag.?.raw; + fn parseTagContent(self: *Parser, allocator: std.mem.Allocator, tag_node: Node) ParserError!?TagNodeBody { + switch (tag_node.tag.?.kind) { + .autoescape => { + const args = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); + const raw_open = tag_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; + 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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); + return ae_node; + }, + .block => { + const block_name_raw = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; + const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); - const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); - return ae_node; - } + const block_node = try self.parseBlockBlock(allocator, block_name, raw_open); + return block_node; + }, + .csrf_token => { + if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { + return error.InvalidCsrfTokenArgs; + } + return TagNodeBody{ + .csrf_token = true, + }; + }, + .cycle => { + const args = tag_node.tag.?.args; - if (std.mem.eql(u8, tag_name, "spaceless")) { - const raw_open = tag_node.tag.?.raw; + var values = std.ArrayList([]const u8){}; + defer values.deinit(allocator); - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); - return spaceless_node; - } + if (i >= args.len) break; - if (std.mem.eql(u8, tag_name, "partialdef")) { - const partial_name = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); - const raw_open = tag_node.tag.?.raw; + 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; + } - const duped_name = try allocator.dupe(u8, partial_name); - errdefer allocator.free(duped_name); + 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; + } - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - const partialdef_node = try self.parsePartialDefBlock(allocator, duped_name, raw_open); - return partialdef_node; - } - - if (std.mem.eql(u8, tag_name, "partial")) { - const partial_name = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n\"'"); - - const duped_name = try allocator.dupe(u8, partial_name); - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .partial, - .partial = .{ .name = duped_name }, - }; - } - - if (std.mem.eql(u8, tag_name, "lorem")) { - const args = tag_node.tag.?.args; - - var count: ?[]const u8 = null; - var method: ?[]const u8 = null; - var format: ?[]const u8 = null; - - var parts = std.mem.splitScalar(u8, args, ' '); - while (parts.next()) |part| { - const trimmed = std.mem.trim(u8, part, " \t\r\n"); - if (trimmed.len == 0) continue; - - const num: usize = std.fmt.parseInt(usize, trimmed, 10) catch 0; - if (num > 0) { - count = try allocator.dupe(u8, trimmed); + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, value); } - if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { - method = try allocator.dupe(u8, trimmed); - } else if (std.mem.eql(u8, trimmed, "html")) { - format = try allocator.dupe(u8, trimmed); + return TagNodeBody{ + .cycle = .{ + .values = try values.toOwnedSlice(allocator), + }, + }; + }, + .debug => { + if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { + return error.InvalidDebugArgs; } - } + return TagNodeBody{ + .debug = true, + }; + }, + .extends => { + const parent = std.mem.trim(u8, tag_node.tag.?.args, " \t\""); + return TagNodeBody{ + .extends = .{ + .parent_name = parent, + }, + }; + }, + .filter_block => { + const filters_raw = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; + const filter_node = try self.parseFilterBlock(allocator, filters_raw, raw_open); + return filter_node; + }, + .firstof => { + const args = tag_node.tag.?.args; - allocator.free(tag_node.tag.?.name); - allocator.free(tag_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) {} - return Node{ - .type = .lorem, - .lorem = .{ - .count = count, - .method = method, - .format = format, - }, - }; - } + if (i >= args.len) break; - if (std.mem.eql(u8, tag_name, "filter")) { - const filters_raw = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; + 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; + } - // DUPE O FILTERS IMEDIATAMENTE - const duped_filters = try allocator.dupe(u8, filters_raw); + 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; + } - // Agora libera a tag open - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, value); + } - const filter_node = try self.parseFilterBlock(allocator, duped_filters, raw_open); - return filter_node; - } + return TagNodeBody{ + .firstof = .{ + .values = try values.toOwnedSlice(allocator), + }, + }; + }, + .for_block => { + const args = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; - if (std.mem.eql(u8, tag_name, "extends")) { - const parent = std.mem.trim(u8, tag_node.tag.?.args, " \t\""); + 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"); - const duped = try allocator.dupe(u8, parent); + const for_node = try self.parseForBlock(allocator, loop_var_raw, iterable_raw, raw_open); + return for_node; + }, + .if_block => { + const args = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; + const if_node = try self.parseIfBlock(allocator, args, raw_open); + return if_node; + }, + .include => { + const args = tag_node.tag.?.args; + var template_path = args; + if (template_path.len >= 2 and template_path[0] == '"' and template_path[template_path.len - 1] == '"') { + template_path = template_path[1 .. template_path.len - 1]; + } + return TagNodeBody{ + .include = .{ + .template_path = template_path, + }, + }; + }, + .load => { + const args = tag_node.tag.?.args; + var libraries = std.ArrayList([]const u8){}; + defer libraries.deinit(allocator); - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - return Node{ - .type = .extends, - .extends = .{ .parent_name = duped }, - }; - } + if (i >= args.len) break; - if (std.mem.eql(u8, tag_name, "block")) { - const block_name_raw = tag_node.tag.?.args; - const raw_open = tag_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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - const block_node = try self.parseBlockBlock(allocator, duped_name, raw_open); - return block_node; - } - - if (std.mem.eql(u8, tag_name, "now")) { - const args = tag_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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .now, - .now = .{ .format = duped_format }, - }; - } - - if (std.mem.eql(u8, tag_name, "with")) { - const args = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - - const assignments = try self.parseAssignments(allocator, args); - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - const with_node = try self.parseWithBlock(allocator, assignments, raw_open); - return with_node; - } - - if (std.mem.eql(u8, tag_name, "include")) { - const args = tag_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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .include, - .include = .{ .template_name = duped_name }, - }; - } - - if (std.mem.eql(u8, tag_name, "if")) { - const condition_raw = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - - const condition = try allocator.dupe(u8, condition_raw); - - // Libera apenas name e args da tag open - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - // NÃO chame node.deinit aqui — raw_open ainda é usado - - const if_node = try self.parseIfBlock(allocator, condition, raw_open); - return if_node; - } - - if (std.mem.eql(u8, tag_name, "for")) { - const args = tag_node.tag.?.args; - const raw_open = tag_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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - const for_node = try self.parseForBlock(allocator, loop_var, iterable, raw_open); - return for_node; - } - - if (std.mem.eql(u8, tag_name, "verbatim")) { - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - const verbatim_node = try self.parseVerbatim(allocator); - return verbatim_node; - } - - if (std.mem.eql(u8, tag_name, "url")) { - const args = tag_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 { + const start = i; while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} + + const lib_name = args[start..i]; + try libraries.append(allocator, lib_name); } - const arg = args[arg_start..i]; - try arg_list.append(allocator, try allocator.dupe(u8, arg)); - } - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + return TagNodeBody{ + .load = .{ + .libraries = try libraries.toOwnedSlice(allocator), + }, + }; + }, + .lorem => { + const args = tag_node.tag.?.args; + var count: ?[]const u8 = null; + var method: ?[]const u8 = null; + var format: ?[]const u8 = null; - return Node{ - .type = .url, - .url = .{ - .name = duped_view, - .args = try arg_list.toOwnedSlice(allocator), - }, - }; - } + var parts = std.mem.splitScalar(u8, args, ' '); + while (parts.next()) |part| { + const trimmed = std.mem.trim(u8, part, " \t\r\n"); + if (trimmed.len == 0) continue; - if (std.mem.eql(u8, tag_name, "cycle")) { - const args = tag_node.tag.?.args; + const num: usize = std.fmt.parseInt(usize, trimmed, 10) catch 0; + if (num > 0) { + count = trimmed; + } - var values = std.ArrayList([]const u8){}; - defer values.deinit(allocator); + if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { + method = trimmed; + } else if (std.mem.eql(u8, trimmed, "html") or std.mem.eql(u8, trimmed, "text")) { + format = trimmed; + } + } - var i: usize = 0; - while (i < args.len) { + return TagNodeBody{ + .lorem = .{ + .count = count, + .method = method, + .format = format, + }, + }; + }, + .now => { + const args = tag_node.tag.?.args; + 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]; + } + return TagNodeBody{ + .now = .{ + .format = format_str, + }, + }; + }, + .partial => { + const args = tag_node.tag.?.args; + var partial_name = args; + if (partial_name.len >= 2 and partial_name[0] == '"' and partial_name[partial_name.len - 1] == '"') { + partial_name = partial_name[1 .. partial_name.len - 1]; + } + return TagNodeBody{ + .partial = .{ + .name = partial_name, + }, + }; + }, + .partialdef => { + const args = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; + const partialdef_node = try self.parsePartialDefBlock(allocator, args, raw_open); + return partialdef_node; + }, + .querystring => { + const args = tag_node.tag.?.args; + var modifications = std.ArrayList([]const u8){}; + defer modifications.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 mod = args[start..i]; + const mod = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try modifications.append(allocator, mod); + } + + return TagNodeBody{ + .querystring = .{ + .modifications = try modifications.toOwnedSlice(allocator), + }, + }; + }, + .regroup => { + const args = tag_node.tag.?.args; + + const by_pos = std.mem.indexOf(u8, args, " by ") orelse return error.InvalidRegroupSyntax; + const as_pos = std.mem.indexOf(u8, args[by_pos + 4 ..], " as ") orelse return error.InvalidRegroupSyntax; + const as_pos_abs = by_pos + 4 + as_pos; + + const source_raw = std.mem.trim(u8, args[0..by_pos], " \t\r\n"); + const by_raw = std.mem.trim(u8, args[by_pos + 4 .. as_pos_abs], " \t\r\n"); + const as_raw = std.mem.trim(u8, args[as_pos_abs + 4 ..], " \t\r\n"); + + const source = source_raw; + const by = by_raw; + const as = as_raw; + + return TagNodeBody{ + .regroup = .{ + .source = source, + .by = by, + .as_var = as, + }, + }; + }, + .resetcycle => { + const args = tag_node.tag.?.args; + var cycle_name = args; + if (cycle_name.len >= 2 and cycle_name[0] == '"' and cycle_name[cycle_name.len - 1] == '"') { + cycle_name = cycle_name[1 .. cycle_name.len - 1]; + } + return TagNodeBody{ + .resetcycle = .{ + .cycle_name = if (args.len > 0) cycle_name else null, + }, + }; + }, + .spaceless => { + const raw_open = tag_node.tag.?.raw; + const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); + return spaceless_node; + }, + .templatetag => { + const args = tag_node.tag.?.args; + const templatetag = TemplateTagNode.parse(args); + if (templatetag == null) return error.InvalidTemplateTag; + + return TagNodeBody{ .templatetag = templatetag.? }; + }, + .url => { + const args = tag_node.tag.?.args; + + var arg_list = std.ArrayList([]const u8){}; + defer arg_list.deinit(allocator); + + var i: usize = 0; + // Pula o nome da url (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 name_start = i; + while (i < args.len and args[i] != '\'') : (i += 1) {} + if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; + const url_name = args[name_start..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; - } + // const duped_view = try allocator.dupe(u8, url_name); + // try arg_list.append(duped_view); while (i < args.len) { - if (in_quote) { - if (args[i] == quote_char) { - i += 1; - break; - } + 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 { - if (std.ascii.isWhitespace(args[i])) break; + while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} } - i += 1; + + const arg = args[arg_start..i]; + // const duped_arg = try allocator.dupe(u8, arg); + try arg_list.append(allocator, arg); } - const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try values.append(allocator, try allocator.dupe(u8, value)); - } + return TagNodeBody{ + .url = .{ + .name = url_name, + .args = try arg_list.toOwnedSlice(allocator), + }, + }; + }, + .verbatim => { + const raw_open = tag_node.tag.?.raw; + const block_name_raw = tag_node.tag.?.args; + const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); + const verbatim_node = try self.parseVerbatimBlock(allocator, block_name, raw_open); + return verbatim_node; + }, + .widthratio => { + const args = tag_node.tag.?.args; + var parts = std.mem.splitScalar(u8, args, ' '); + var value_part: ?[]const u8 = null; + var max_part: ?[]const u8 = null; + var divisor_part: ?[]const u8 = null; - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); + if (parts.next()) |p| value_part = std.mem.trim(u8, p, " \t\r\n"); + if (parts.next()) |p| max_part = std.mem.trim(u8, p, " \t\r\n"); + if (parts.next()) |p| divisor_part = std.mem.trim(u8, p, " \t\r\n"); - return Node{ - .type = .cycle, - .cycle = .{ - .values = try values.toOwnedSlice(allocator), - }, - }; + if (value_part == null or max_part == null) return error.InvalidWidthRatioSyntax; + + const value = value_part.?; + const max_value = max_part.?; + const divisor = if (divisor_part) |d| d else null; + + return TagNodeBody{ + .widthratio = .{ + .value = value, + .max_value = max_value, + .divisor = divisor, + }, + }; + }, + .with_block => { + const args = tag_node.tag.?.args; + const raw_open = tag_node.tag.?.raw; + + const assignments = try self.parseAssignments(allocator, args); + const with_node = try self.parseWithBlock(allocator, assignments, raw_open); + return with_node; + }, + else => return null, } + // if (std.mem.eql(u8, tag_name, "with")) { + // const args = tag_node.tag.?.args; + // const raw_open = tag_node.tag.?.raw; + // + // const assignments = try self.parseAssignments(allocator, args); + // + // allocator.free(tag_node.tag.?.name); + // allocator.free(tag_node.tag.?.args); + // + // const with_node = try self.parseWithBlock(allocator, assignments, raw_open); + // return with_node; + // } + // + // + // if (std.mem.eql(u8, tag_name, "verbatim")) { + // allocator.free(tag_node.tag.?.name); + // allocator.free(tag_node.tag.?.args); + // + // const verbatim_node = try self.parseVerbatim(allocator); + // return verbatim_node; + // } - if (std.mem.eql(u8, tag_name, "firstof")) { - const args = tag_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(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .firstof, - .firstof = .{ - .values = try values.toOwnedSlice(allocator), - }, - }; - } - - if (std.mem.eql(u8, tag_name, "load")) { - const args = tag_node.tag.?.args; - - var libraries = std.ArrayList([]const u8){}; - defer libraries.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; - while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} - - const lib_name = args[start..i]; - try libraries.append(allocator, try allocator.dupe(u8, lib_name)); - } - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .load, - .load = .{ - .libraries = try libraries.toOwnedSlice(allocator), - }, - }; - } - - if (std.mem.eql(u8, tag_name, "debug")) { - // Verifica se tem argumentos (não deve ter) - if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { - return error.InvalidDebugArgs; - } - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .debug, - .debug = true, - }; - } - - if (std.mem.eql(u8, tag_name, "querystring")) { - const args = tag_node.tag.?.args; - - var modifications = std.ArrayList([]const u8){}; - defer modifications.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 mod_str = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try modifications.append(allocator, try allocator.dupe(u8, mod_str)); - } - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .querystring, - .querystring = .{ - .modifications = try modifications.toOwnedSlice(allocator), - }, - }; - } - - if (std.mem.eql(u8, tag_name, "regroup")) { - const args = tag_node.tag.?.args; - - // Esperado: lista by atributo as nome_grupo - const by_pos = std.mem.indexOf(u8, args, " by ") orelse return error.InvalidRegroupSyntax; - const as_pos = std.mem.indexOf(u8, args[by_pos + 4 ..], " as ") orelse return error.InvalidRegroupSyntax; - const as_pos_abs = by_pos + 4 + as_pos; - - const source_raw = std.mem.trim(u8, args[0..by_pos], " \t\r\n"); - const by_raw = std.mem.trim(u8, args[by_pos + 4 .. as_pos_abs], " \t\r\n"); - const as_raw = std.mem.trim(u8, args[as_pos_abs + 4 ..], " \t\r\n"); - - const source = try allocator.dupe(u8, source_raw); - const by = try allocator.dupe(u8, by_raw); - const as_var = try allocator.dupe(u8, as_raw); - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .regroup, - .regroup = .{ - .source = source, - .by = by, - .as_var = as_var, - }, - }; - } - - if (std.mem.eql(u8, tag_name, "resetcycle")) { - const args = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); - - var cycle_name: ?[]const u8 = null; - if (args.len > 0) { - cycle_name = try allocator.dupe(u8, args); - } - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .resetcycle, - .resetcycle = .{ - .cycle_name = cycle_name, - }, - }; - } - - if (std.mem.eql(u8, tag_name, "widthratio")) { - const args = tag_node.tag.?.args; - - var parts = std.mem.splitScalar(u8, args, ' '); - var value_part: ?[]const u8 = null; - var max_part: ?[]const u8 = null; - var divisor_part: ?[]const u8 = null; - - if (parts.next()) |p| value_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| max_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| divisor_part = std.mem.trim(u8, p, " \t\r\n"); - - if (value_part == null or max_part == null) return error.InvalidWidthRatioSyntax; - - const value = try allocator.dupe(u8, value_part.?); - const max_value = try allocator.dupe(u8, max_part.?); - const divisor = if (divisor_part) |d| try allocator.dupe(u8, d) else null; - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .widthratio, - .widthratio = .{ - .value = value, - .max_value = max_value, - .divisor = divisor, - }, - }; - } - - if (std.mem.eql(u8, tag_name, "templatetag")) { - const arg = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); - - const templatetag = TemplateTagNode.parse(arg); - if (templatetag == null) return error.InvalidTemplateTag; - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .templatetag, - .templatetag = templatetag, - }; - } - - if (std.mem.eql(u8, tag_name, "csrf_token")) { - // Verifica se tem argumentos (não deve ter) - if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { - return error.InvalidCsrfTokenArgs; - } - - allocator.free(tag_node.tag.?.name); - allocator.free(tag_node.tag.?.args); - - return Node{ - .type = .csrf_token, - .csrf_token = true, - }; - } - return null; + // return null; } pub fn parse(self: *Parser, allocator: std.mem.Allocator) ParserError![]Node { @@ -2132,20 +1972,18 @@ pub const Parser = struct { defer list.deinit(allocator); while (self.pos < self.template.len) { - if (try self.parseTag(allocator)) |node| { - const tag_name = node.tag.?.name; - - if (std.mem.eql(u8, tag_name, "comment")) { - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); + var tag = try self.parseTag(allocator); + if (tag != null) { + if (tag.?.tag.?.kind == .comment) { try self.parseComment(); continue; } else { - const node_parsed: ?Node = self.parseTagContent(allocator, node) catch null; - if (node_parsed) |n| { - try list.append(allocator, n); - continue; + std.log.debug("Tag: {s}", .{tag.?.tag.?.raw}); + if (try self.parseTagContent(allocator, tag.?)) |tn| { + tag.?.tag.?.body = tn; + try list.append(allocator, tag.?); } + continue; } } @@ -2164,661 +2002,6 @@ pub const Parser = struct { return try list.toOwnedSlice(allocator); } - - pub fn parse_(self: *Parser, allocator: std.mem.Allocator) ParserError![]Node { - var list = std.ArrayList(Node){}; - defer list.deinit(allocator); - - while (self.pos < self.template.len) { - if (try self.parseTag(allocator)) |node| { - const tag_name = node.tag.?.name; - try self.parseTagContent(allocator, node); - - 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); - - 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); - - const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); - try list.append(allocator, spaceless_node); - continue; - } - - if (std.mem.eql(u8, tag_name, "comment")) { - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - try self.parseComment(); - continue; - } - - if (std.mem.eql(u8, tag_name, "partialdef")) { - const partial_name = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); - const raw_open = node.tag.?.raw; - - const duped_name = try allocator.dupe(u8, partial_name); - errdefer allocator.free(duped_name); - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - const partialdef_node = try self.parsePartialDefBlock(allocator, duped_name, raw_open); - try list.append(allocator, partialdef_node); - continue; - } - - if (std.mem.eql(u8, tag_name, "partial")) { - const partial_name = std.mem.trim(u8, node.tag.?.args, " \t\r\n\"'"); - - const duped_name = try allocator.dupe(u8, partial_name); - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .partial, - .partial = .{ .name = duped_name }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "lorem")) { - const args = node.tag.?.args; - - var count: ?[]const u8 = null; - var method: ?[]const u8 = null; - var format: ?[]const u8 = null; - - var parts = std.mem.splitScalar(u8, args, ' '); - while (parts.next()) |part| { - const trimmed = std.mem.trim(u8, part, " \t\r\n"); - if (trimmed.len == 0) continue; - - const num: usize = std.fmt.parseInt(usize, trimmed, 10) catch 0; - if (num > 0) { - count = try allocator.dupe(u8, trimmed); - } - - if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { - method = try allocator.dupe(u8, trimmed); - } else if (std.mem.eql(u8, trimmed, "html")) { - format = try allocator.dupe(u8, trimmed); - } - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .lorem, - .lorem = .{ - .count = count, - .method = method, - .format = format, - }, - }); - 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); - - 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); - - 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); - - 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); - - 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); - 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); - - 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); - - 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); - - 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); - - 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); - - try list.append(allocator, Node{ - .type = .url, - .url = .{ - .name = duped_view, - .args = try arg_list.toOwnedSlice(allocator), - }, - }); - 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); - - 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); - - try list.append(allocator, Node{ - .type = .firstof, - .firstof = .{ - .values = try values.toOwnedSlice(allocator), - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "load")) { - const args = node.tag.?.args; - - var libraries = std.ArrayList([]const u8){}; - defer libraries.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; - while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} - - const lib_name = args[start..i]; - try libraries.append(allocator, try allocator.dupe(u8, lib_name)); - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .load, - .load = .{ - .libraries = try libraries.toOwnedSlice(allocator), - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "debug")) { - // Verifica se tem argumentos (não deve ter) - if (node.tag.?.args.len > 0 and !std.mem.allEqual(u8, node.tag.?.args, ' ')) { - return error.InvalidDebugArgs; - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .debug, - .debug = true, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "querystring")) { - const args = node.tag.?.args; - - var modifications = std.ArrayList([]const u8){}; - defer modifications.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 mod_str = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try modifications.append(allocator, try allocator.dupe(u8, mod_str)); - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .querystring, - .querystring = .{ - .modifications = try modifications.toOwnedSlice(allocator), - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "regroup")) { - const args = node.tag.?.args; - - // Esperado: lista by atributo as nome_grupo - const by_pos = std.mem.indexOf(u8, args, " by ") orelse return error.InvalidRegroupSyntax; - const as_pos = std.mem.indexOf(u8, args[by_pos + 4 ..], " as ") orelse return error.InvalidRegroupSyntax; - const as_pos_abs = by_pos + 4 + as_pos; - - const source_raw = std.mem.trim(u8, args[0..by_pos], " \t\r\n"); - const by_raw = std.mem.trim(u8, args[by_pos + 4 .. as_pos_abs], " \t\r\n"); - const as_raw = std.mem.trim(u8, args[as_pos_abs + 4 ..], " \t\r\n"); - - const source = try allocator.dupe(u8, source_raw); - const by = try allocator.dupe(u8, by_raw); - const as_var = try allocator.dupe(u8, as_raw); - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .regroup, - .regroup = .{ - .source = source, - .by = by, - .as_var = as_var, - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "resetcycle")) { - const args = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); - - var cycle_name: ?[]const u8 = null; - if (args.len > 0) { - cycle_name = try allocator.dupe(u8, args); - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .resetcycle, - .resetcycle = .{ - .cycle_name = cycle_name, - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "widthratio")) { - const args = node.tag.?.args; - - var parts = std.mem.splitScalar(u8, args, ' '); - var value_part: ?[]const u8 = null; - var max_part: ?[]const u8 = null; - var divisor_part: ?[]const u8 = null; - - if (parts.next()) |p| value_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| max_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| divisor_part = std.mem.trim(u8, p, " \t\r\n"); - - if (value_part == null or max_part == null) return error.InvalidWidthRatioSyntax; - - const value = try allocator.dupe(u8, value_part.?); - const max_value = try allocator.dupe(u8, max_part.?); - const divisor = if (divisor_part) |d| try allocator.dupe(u8, d) else null; - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .widthratio, - .widthratio = .{ - .value = value, - .max_value = max_value, - .divisor = divisor, - }, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "templatetag")) { - const arg = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); - - const templatetag = TemplateTagNode.parse(arg); - if (templatetag == null) return error.InvalidTemplateTag; - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .templatetag, - .templatetag = templatetag, - }); - continue; - } - - if (std.mem.eql(u8, tag_name, "csrf_token")) { - // Verifica se tem argumentos (não deve ter) - if (node.tag.?.args.len > 0 and !std.mem.allEqual(u8, node.tag.?.args, ' ')) { - return error.InvalidCsrfTokenArgs; - } - - allocator.free(node.tag.?.name); - allocator.free(node.tag.?.args); - - try list.append(allocator, Node{ - .type = .csrf_token, - .csrf_token = true, - }); - continue; - } - - // Para tags normais - try list.append(allocator, node); - continue; - } - - if (try self.parseVariable(allocator)) |node| { - try list.append(allocator, node); - continue; - } - - if (try self.parseText(allocator)) |node| { - 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_t(allocator: std.mem.Allocator, template: []const u8) ParserError![]Node { diff --git a/src/parser_test.zig b/src/parser_test.zig index 9a57ddb..98dc220 100644 --- a/src/parser_test.zig +++ b/src/parser_test.zig @@ -2,10 +2,13 @@ const std = @import("std"); const testing = std.testing; const parser = @import("parser.zig"); -test "parse texto simples" { +test "parse simple text" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("1 - parse simple text\n", .{}); const allocator = testing.allocator; const template = "Olá mundo!"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| { node.deinit(allocator); @@ -18,10 +21,13 @@ test "parse texto simples" { try testing.expectEqualStrings("Olá mundo!", nodes[0].text.?.content); } -test "parse variável simples" { +test "parse simple var" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("2 - parse simple var\n", .{}); const allocator = testing.allocator; const template = "Olá {{ nome }}!"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| { node.deinit(allocator); @@ -41,10 +47,13 @@ test "parse variável simples" { try testing.expectEqualStrings("!", nodes[2].text.?.content); } -test "parse variável com espaços" { +test "parse var with spaces" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("3 - parse var with spaces\n", .{}); const allocator = testing.allocator; const template = "{{ espacos }}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| { node.deinit(allocator); @@ -57,331 +66,356 @@ test "parse variável com espaços" { try testing.expectEqualStrings("espacos", nodes[0].variable.?.expr); } -test "parse tag simples" { +test "parse autoescape on" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("4 - parse autoescape on\n", .{}); const allocator = testing.allocator; - const template = "Antes {% minha_tag %} Depois"; - const nodes = try parser.parse(allocator, template); + const template = "{% autoescape on %}Texto negrito{% endautoescape %}"; + const expected = "Texto negrito"; + + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { - for (nodes) |node| { - node.deinit(allocator); - } + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + if (nodes[0].tag) |tag| { + try testing.expect(tag.body.autoescape.enabled == true); + try testing.expectEqual(@as(usize, 1), tag.body.autoescape.body.len); + try testing.expectEqualStrings(expected, tag.body.autoescape.body[0].text.?.content); + } +} + +test "parse autoescape off" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("5 - parse autoescape off\n", .{}); + const allocator = testing.allocator; + const template = "{% autoescape off %}Texto negrito{% endautoescape %}"; + const expected = "Texto negrito"; + + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + if (nodes[0].tag) |tag| { + try testing.expect(tag.body.autoescape.enabled == false); + try testing.expectEqual(@as(usize, 1), tag.body.autoescape.body.len); + try testing.expectEqualStrings(expected, tag.body.autoescape.body[0].text.?.content); + } +} +test "parse simple block" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("6 - parse simple block\n", .{}); + const allocator = testing.allocator; + const template = "{% block titulo %}Meu Título{% endblock %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expectEqual(nodes[0].tag.?.kind, .block); + try testing.expectEqualStrings("titulo", nodes[0].tag.?.body.block.name); + const b = nodes[0].tag.?.body.block.body; + try testing.expectEqual(@as(usize, 1), b.len); + try testing.expectEqual(b[0].type, .text); + try testing.expectEqualStrings("Meu Título", b[0].text.?.content); +} + +test "parse block with super" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("7 - parse block with super\n", .{}); + + const allocator = testing.allocator; + const template = "{% block conteudo %}{{ block.super }} Conteúdo filho{% endblock %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expectEqualStrings("conteudo", nodes[0].tag.?.body.block.name); + const b = nodes[0].tag.?.body.block.body; + try testing.expectEqual(@as(usize, 2), b.len); + try testing.expect(b[0].type == .tag); + try testing.expect(b[0].tag.?.kind == .super); + try testing.expect(b[1].type == .text); + try testing.expectEqualStrings(" Conteúdo filho", b[1].text.?.content); +} + +// TODO: check it +test "parse block with block inside" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("8 - parse block with block inside\n", .{}); + + const allocator = testing.allocator; + const template = "{% block conteudo %}{% block inner %}Conteúdo filho{% endblock %}{% endblock %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expectEqualStrings("conteudo", nodes[0].tag.?.body.block.name); + const b = nodes[0].tag.?.body.block.body; + try testing.expectEqual(@as(usize, 3), b.len); + try testing.expect(b[0].type == .tag); + try testing.expect(b[0].tag.?.kind == .block); + try testing.expect(b[1].type == .text); + try testing.expectEqualStrings("Conteúdo filho", b[1].text.?.content); + try testing.expect(b[2].type == .tag); + // std.debug.print("block:\n {any}",.{nodes[0].tag.?.body.block}); + // for(b) |node| std.debug.print("block:\n {any}\n",.{node}); + // try testing.expectEqualStrings("inner",b[0].tag.?.body.block.name); + // try testing.expect(b[1].type == .text); + // try testing.expectEqualStrings(" Conteúdo filho",b[1].text.?.content); +} + +test "parse simple csrf_token" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("9 - parse simple csrf_token\n", .{}); + + const allocator = testing.allocator; + const template = "
"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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.expectEqualStrings("", nodes[2].text.?.content); } -test "parse if block básico" { +test "parse just csrf_token" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("10 - parse just csrf_token\n", .{}); + 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); + const template = "{% csrf_token %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); 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); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .csrf_token); } test "parse comment" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("11 - parse comment\n", .{}); + const allocator = testing.allocator; const template = "Bazinga! {% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); 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" { +// FIX: check parse block something is wrong +test "parse comment inside block" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("12 - parse comment\n", .{}); + const allocator = testing.allocator; - const template = "Cabeçalho {% include \"header.zdt\" %} Conteúdo {% include \"footer.zdt\" %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% block conteudo %}Bazinga!{% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %} haha{% endblock %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } - try testing.expectEqual(@as(usize, 4), nodes.len); + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + // try testing.expectEqual(@as(usize,2), nodes[0].tag.?.body.block.body.len); + // + // const b = nodes[0].tag.?.body.block.body; + // for (b) |node| { + // if (node.type == .text) std.debug.print("{s}\n", .{node.text.?.content}); + // std.debug.print("{any}\n", .{node}); + // } + // // std.debug.print("{any}",.{nodes[0].tag.?.body.block.body}); + try testing.expect(nodes[0].tag.?.body.block.body[0].type == .text); + try testing.expectEqualStrings("Bazinga!", nodes[0].tag.?.body.block.body[0].text.?.content); +} + +test "parse simple cycle" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("13 - parse simple cycle\n", .{}); + + const allocator = testing.allocator; + const template = "{% cycle 'row1' 'row2' %}"; + + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + + const args = nodes[0].tag.?.body.cycle.values; + try testing.expectEqual(@as(usize, 2), args.len); + try testing.expectEqualStrings("row1", args[0]); + try testing.expectEqualStrings("row2", args[1]); +} + +test "parse cycle values without quotes" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("14 - parse cycle values without quotes\n", .{}); + + const allocator = testing.allocator; + const template = "{% cycle row1 row2 row3 %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + const args = nodes[0].tag.?.body.cycle.values; + try testing.expectEqual(@as(usize, 3), args.len); + try testing.expectEqualStrings("row1", args[0]); + try testing.expectEqualStrings("row2", args[1]); + try testing.expectEqualStrings("row3", args[2]); +} + +test "parse simple debug" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("15 - parse simple debug\n", .{}); + + const allocator = testing.allocator; + const template = "Antes {% debug %} Depois"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Cabeçalho ", nodes[0].text.?.content); + try testing.expectEqualStrings("Antes ", 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[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .debug); 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); + try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); } test "parse extends" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("16 - parse extends\n", .{}); + const allocator = testing.allocator; - const template = "{% extends \"base.zdt\" %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% extends \"base.html\" %}"; + + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); 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); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .extends); + try testing.expectEqualStrings("base.html", nodes[0].tag.?.body.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); - } +test "parse simple filter block" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("17 - parse simple filter block\n", .{}); - 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); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); 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.expect(nodes[0].type == .tag); + const fb = nodes[0].tag.?.body.filter_block; + try testing.expectEqualStrings("upper", fb.filters); 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" { +test "parse filter block multiple filters" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("18 - parse filter block multiple filters\n", .{}); + const allocator = testing.allocator; const template = "{% filter upper|escape %}Conteúdo negrito{% endfilter %}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); 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.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .filter_block); + const fb = nodes[0].tag.?.body.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" { +test "parse variable with filters" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("19 - parse variable with filters\n", .{}); + const allocator = testing.allocator; const template = "{{ nome|upper|default:\"Visitante\" }}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); @@ -398,521 +432,832 @@ test "parse variable com filtros" { 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); - } +test "parse simple firstof" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("20 - parse simple firstof\n", .{}); - 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]); -} - -test "parse load simples" { - const allocator = testing.allocator; - const template = "{% load i18n humanize %}"; - 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 == .load); - const l = nodes[0].load.?; - try testing.expectEqual(@as(usize, 2), l.libraries.len); - try testing.expectEqualStrings("i18n", l.libraries[0]); - try testing.expectEqualStrings("humanize", l.libraries[1]); -} - -test "parse load com múltiplas" { - const allocator = testing.allocator; - const template = "{% load admin_urls static %}"; - 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 == .load); - const l = nodes[0].load.?; - try testing.expectEqual(@as(usize, 2), l.libraries.len); - try testing.expectEqualStrings("admin_urls", l.libraries[0]); - try testing.expectEqualStrings("static", l.libraries[1]); -} - -test "parse csrf_token simples" { - const allocator = testing.allocator; - const template = ""; - 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("", nodes[2].text.?.content); -} - -test "parse csrf_token sozinho" { - const allocator = testing.allocator; - const template = "{% csrf_token %}"; - 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 == .csrf_token); -} - -test "parse lorem padrão" { - const allocator = testing.allocator; - const template = "{% lorem %}"; - 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 == .lorem); - const l = nodes[0].lorem.?; - try testing.expect(l.count == null); - try testing.expect(l.method == null); - try testing.expect(l.format == null); -} - -test "parse lorem com argumentos" { - const allocator = testing.allocator; - const template = "{% lorem 5 p html %}"; - 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 == .lorem); - const l = nodes[0].lorem.?; - try testing.expectEqualStrings("5", l.count.?); - try testing.expectEqualStrings("p", l.method.?); - try testing.expectEqualStrings("html", l.format.?); -} - -test "parse debug simples" { - const allocator = testing.allocator; - const template = "Antes {% debug %} 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 == .debug); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); -} - -test "parse debug sozinho" { - const allocator = testing.allocator; - const template = "{% debug %}"; - 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 == .debug); -} - -test "parse partialdef simples" { - const allocator = testing.allocator; - const template = "{% partialdef cabecalho %}Cabeçalho{% endpartialdef %}"; - 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 == .partialdef); - const pd = nodes[0].partialdef.?; - try testing.expectEqualStrings("cabecalho", pd.name); - try testing.expectEqual(@as(usize, 1), pd.body.len); - try testing.expectEqualStrings("Cabeçalho", pd.body[0].text.?.content); -} - -test "parse partial uso" { - const allocator = testing.allocator; - const template = "Início {% partial \"cabecalho\" %} 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 == .partial); - try testing.expectEqualStrings("cabecalho", nodes[1].partial.?.name); -} - -test "parse querystring simples" { - const allocator = testing.allocator; - const template = "Link: {% querystring \"page=2\" %}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 2), nodes.len); - try testing.expect(nodes[1].type == .querystring); - const qs = nodes[1].querystring.?; - try testing.expectEqual(@as(usize, 1), qs.modifications.len); - try testing.expectEqualStrings("page=2", qs.modifications[0]); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Valor: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + const fo = nodes[1].tag.?.body.firstof.values; + try testing.expectEqual(@as(usize, 3), fo.len); + try testing.expectEqualStrings("var1", fo[0]); + try testing.expectEqualStrings("var2", fo[1]); + try testing.expectEqualStrings("var3", fo[2]); } -test "parse querystring múltiplos" { +test "parse firstof with fallback" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("21 - parse firstof with fallback\n", .{}); + const allocator = testing.allocator; - const template = "{% querystring \"ordenar=-nome\" \"pagina\" None %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% firstof var1 var2 \"Nenhum valor\" %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .querystring); - const qs = nodes[0].querystring.?; - try testing.expectEqual(@as(usize, 3), qs.modifications.len); - try testing.expectEqualStrings("ordenar=-nome", qs.modifications[0]); - try testing.expectEqualStrings("pagina", qs.modifications[1]); - try testing.expectEqualStrings("None", qs.modifications[2]); + try testing.expect(nodes[0].type == .tag); + const fo = nodes[0].tag.?.body.firstof.values; + try testing.expectEqual(@as(usize, 3), fo.len); + try testing.expectEqualStrings("var1", fo[0]); + try testing.expectEqualStrings("var2", fo[1]); + try testing.expectEqualStrings("Nenhum valor", fo[2]); } -test "parse regroup simples" { +test "parse for block whithout empty" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("22 - parse for block whithout empty\n", .{}); + const allocator = testing.allocator; - const template = "{% regroup pessoas by cidade as grupos_cidade %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% for item in lista %}Item: {{ item }}{% endfor %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .regroup); - const r = nodes[0].regroup.?; - try testing.expectEqualStrings("pessoas", r.source); - try testing.expectEqualStrings("cidade", r.by); - try testing.expectEqualStrings("grupos_cidade", r.as_var); + try testing.expect(nodes[0].type == .tag); + const fb = nodes[0].tag.?.body.@"for"; + try testing.expectEqualStrings("item", fb.loop_var); + try testing.expectEqualStrings("lista", fb.iterable); + try testing.expectEqual(@as(usize, 2), fb.body.len); + 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 resetcycle simples" { +test "parse for block with empty" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("23 - parse for block with empty\n", .{}); + const allocator = testing.allocator; - const template = "{% resetcycle %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% for item in lista %}Tem{% empty %}Vazio{% endfor %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .resetcycle); - const rc = nodes[0].resetcycle.?; - try testing.expect(rc.cycle_name == null); + try testing.expect(nodes[0].type == .tag); + const fb = nodes[0].tag.?.body.@"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 resetcycle com nome" { +test "parse basic if block" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("24 - parse basic if block\n", .{}); + const allocator = testing.allocator; - const template = "{% resetcycle rowclass %}"; - const nodes = try parser.parse(allocator, template); + const template = "{% if user.logged %}Welcome!{% endif %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + + const ib = nodes[0].tag.?.body.@"if"; + try testing.expectEqualStrings("user.logged", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expect(ib.true_body[0].type == .text); + try testing.expectEqualStrings("Welcome!", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 0), ib.false_body.len); +} + +test "parse if block whithout else" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("25 - parse if block whithout else\n", .{}); + + const allocator = testing.allocator; + const template = "{% if cond %}Verdadeiro{% endif %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + const ib = nodes[0].tag.?.body.@"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 with else" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("26 - parse if block with else\n", .{}); + + const allocator = testing.allocator; + const template = "{% if cond %}Verdadeiro{% else %}Falso{% endif %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .if_block); + const ib = nodes[0].tag.?.body.@"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 simple include" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("27 - parse simple include\n", .{}); + + const allocator = testing.allocator; + const template = "Cabeçalho {% include \"header.html\" %} Conteúdo {% include \"footer.html\" %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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 == .tag); + try testing.expect(nodes[1].tag.?.kind == .include); + try testing.expectEqualStrings("header.html", nodes[1].tag.?.body.include.template_path); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Conteúdo ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .tag); + try testing.expectEqualStrings("footer.html", nodes[3].tag.?.body.include.template_path); +} + +test "parse include without quotes (it is ok for now)" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("28 - parse include without quotes (it is ok for now)\n", .{}); + + const allocator = testing.allocator; + const template = "{% include header.html %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .resetcycle); - const rc = nodes[0].resetcycle.?; - try testing.expect(rc.cycle_name != null); - try testing.expectEqualStrings("rowclass", rc.cycle_name.?); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .include); + try testing.expectEqualStrings("header.html", nodes[0].tag.?.body.include.template_path); } -test "parse widthratio simples" { +test "parse simple load" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("29 - parse simple load\n", .{}); + const allocator = testing.allocator; - const template = "Progresso: {% widthratio progresso 0 100 %}%"; - const nodes = try parser.parse(allocator, template); + const template = "{% load i18n %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .load); + const l = nodes[0].tag.?.body.load; + try testing.expectEqual(@as(usize, 1), l.libraries.len); + try testing.expectEqualStrings("i18n", l.libraries[0]); +} + +test "parse load with multiple libraries" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("30 - parse load with multiple libraries\n", .{}); + + const allocator = testing.allocator; + const template = "{% load admin_urls static %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .load); + const l = nodes[0].tag.?.body.load; + try testing.expectEqual(@as(usize, 2), l.libraries.len); + try testing.expectEqualStrings("admin_urls", l.libraries[0]); + try testing.expectEqualStrings("static", l.libraries[1]); +} +test "parse simple lorem" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("31 - parse simple lorem\n", .{}); + + const allocator = testing.allocator; + const template = "{% lorem %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .lorem); + const l = nodes[0].tag.?.body.lorem; + try testing.expect(l.count == null); + try testing.expect(l.method == null); + try testing.expect(l.format == null); +} + +test "parse lorem with arguments" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("32 - parse lorem with arguments\n", .{}); + + const allocator = testing.allocator; + const template = "{% lorem 5 p html %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .lorem); + const l = nodes[0].tag.?.body.lorem; + try testing.expectEqualStrings("5", l.count.?); + try testing.expectEqualStrings("p", l.method.?); + try testing.expectEqualStrings("html", l.format.?); +} + +test "parse simple now" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("33 - parse simple now\n", .{}); + + const allocator = testing.allocator; + const template = "Data atual: {% now \"Y-m-d\" %} às {% now \"H:i\" %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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 == .tag); + try testing.expect(nodes[1].tag.?.kind == .now); + try testing.expectEqualStrings("Y-m-d", nodes[1].tag.?.body.now.format); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" às ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .tag); + try testing.expect(nodes[3].tag.?.kind == .now); + try testing.expectEqualStrings("H:i", nodes[3].tag.?.body.now.format); +} + +test "parse now whithout quotes" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("34 - parse now whithout quotes\n", .{}); + + const allocator = testing.allocator; + const template = "{% now Y-m-d %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .now); + try testing.expectEqualStrings("Y-m-d", nodes[0].tag.?.body.now.format); +} + +test "parse simple partial" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("35 - parse simple partial\n", .{}); + + const allocator = testing.allocator; + const template = "Início {% partial \"cabecalho\" %} Fim"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 3), nodes.len); - try testing.expect(nodes[1].type == .widthratio); - const wr = nodes[1].widthratio.?; - try testing.expectEqualStrings("progresso", wr.value); - try testing.expectEqualStrings("0", wr.max_value); - try testing.expectEqualStrings("100", wr.divisor.?); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Início ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .partial); + try testing.expectEqualStrings("cabecalho", nodes[1].tag.?.body.partial.name); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); } -test "parse widthratio sem divisor" { +test "parse partial whithout quotes" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("36 - parse partial whithout quotes\n", .{}); + const allocator = testing.allocator; - const template = "{% widthratio valor 100 %}"; - const nodes = try parser.parse(allocator, template); + const template = "Início {% partial cabecalho %} Fim"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Início ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .partial); + try testing.expectEqualStrings("cabecalho", nodes[1].tag.?.body.partial.name); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); +} + +test "parse simple partialdef" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("37 - parse simple partialdef\n", .{}); + + const allocator = testing.allocator; + const template = "{% partialdef cabecalho %}Cabeçalho{% endpartialdef %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .widthratio); - const wr = nodes[0].widthratio.?; - try testing.expectEqualStrings("valor", wr.value); - try testing.expectEqualStrings("100", wr.max_value); - try testing.expect(wr.divisor == null); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .partialdef); + const pd = nodes[0].tag.?.body.partialdef; + try testing.expectEqualStrings("cabecalho", pd.name); + try testing.expectEqual(@as(usize, 1), pd.body.len); + try testing.expectEqualStrings("Cabeçalho", pd.body[0].text.?.content); } +test "parse simple querystring" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("38 - parse simple querystring\n", .{}); + + const allocator = testing.allocator; + const template = "Link: {% querystring \"page=2\" %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 2), nodes.len); + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .querystring); + const qs = nodes[1].tag.?.body.querystring; + try testing.expectEqual(@as(usize, 1), qs.modifications.len); + try testing.expectEqualStrings("page=2", qs.modifications[0]); +} + +test "parse querystring with multiple modifications" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("39 - parse querystring with multiple modifications\n", .{}); + + const allocator = testing.allocator; + const template = "{% querystring \"ordenar=-nome\" \"pagina\" None %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .querystring); + const qs = nodes[0].tag.?.body.querystring; + try testing.expectEqual(@as(usize, 3), qs.modifications.len); + try testing.expectEqualStrings("ordenar=-nome", qs.modifications[0]); + try testing.expectEqualStrings("pagina", qs.modifications[1]); + try testing.expectEqualStrings("None", qs.modifications[2]); +} + +test "parse simple regroup" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("40 - parse simple regroup\n", .{}); + + const allocator = testing.allocator; + const template = "{% regroup pessoas by cidade as grupos_cidade %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .regroup); + const r = nodes[0].tag.?.body.regroup; + try testing.expectEqualStrings("pessoas", r.source); + try testing.expectEqualStrings("cidade", r.by); + try testing.expectEqualStrings("grupos_cidade", r.as_var); +} + +test "parse simple resetcycle" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("41 - parse simple resetcycle\n", .{}); + + const allocator = testing.allocator; + const template = "{% resetcycle %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .resetcycle); + const rc = nodes[0].tag.?.body.resetcycle; + try testing.expect(rc.cycle_name == null); +} + +test "parse resetcycle with name" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("42 - parse resetcycle with name\n", .{}); + + const allocator = testing.allocator; + const template = "{% resetcycle rowclass %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .resetcycle); + const rc = nodes[0].tag.?.body.resetcycle; + try testing.expect(rc.cycle_name != null); + try testing.expectEqualStrings("rowclass", rc.cycle_name.?); +} +test "parse simple spaceless" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("43 - parse simple spaceless\n", .{}); + + const allocator = testing.allocator; + const template = "{% spaceless %}{% endspaceless %}"; + + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .spaceless); + const sl = nodes[0].tag.?.body.spaceless; + try testing.expectEqual(@as(usize, 1), sl.body.len); + try testing.expectEqualStrings("", sl.body[0].text.?.content); +} + +// TODO: check nested spaceless +test "parse spaceless nested" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("44 - parse spaceless nested\n", .{}); + + const allocator = testing.allocator; + const template = "{% spaceless %}Outer {% spaceless %}Inner{% endspaceless %} Outer{% endspaceless %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .spaceless); + const sl = nodes[0].tag.?.body.spaceless; + // for (sl.body) |node| { + // if (node.type == .text) { + // std.debug.print("{s}\n", .{node.text.?.content}); + // } else { + // std.debug.print("{any}\n", .{node}); + // } + // } + try testing.expectEqual(@as(usize, 4), sl.body.len); +} test "parse templatetag openblock" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("45 - parse templatetag openblock\n", .{}); + const allocator = testing.allocator; const template = "{% templatetag openblock %}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .templatetag); - try testing.expect(nodes[0].templatetag.?.kind == .openblock); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .templatetag); + try testing.expect(nodes[0].tag.?.body.templatetag.kind == .openblock); } test "parse templatetag closevariable" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("46 - parse templatetag closevariable\n", .{}); + const allocator = testing.allocator; const template = "{% templatetag closevariable %}"; - const nodes = try parser.parse(allocator, template); + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .templatetag); - try testing.expect(nodes[0].templatetag.?.kind == .closevariable); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .templatetag); + try testing.expect(nodes[0].tag.?.body.templatetag.kind == .closevariable); } + +test "parse simple url" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("47 - parse simple url\n", .{}); + + const allocator = testing.allocator; + const template = "Link: {% url 'home' %} Fim"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Link: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .url); + const u = nodes[1].tag.?.body.url; + try testing.expectEqualStrings("home", u.name); + try testing.expectEqual(@as(usize, 0), u.args.len); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); +} + +test "parse url with arguments" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("48 - parse url with arguments\n", .{}); + + const allocator = testing.allocator; + const template = "{% url 'post_detail' post.id \"comentario\" %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .url); + const u = nodes[0].tag.?.body.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 simple widthratio" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("49 - parse simple widthratio\n", .{}); + + const allocator = testing.allocator; + const template = "Progresso: {% widthratio progresso 0 100 %}%"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Progresso: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .widthratio); + const wr = nodes[1].tag.?.body.widthratio; + try testing.expectEqualStrings("progresso", wr.value); + try testing.expectEqualStrings("0", wr.max_value); + try testing.expectEqualStrings("100", wr.divisor.?); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings("%", nodes[2].text.?.content); +} + +test "parse simple widthratio without divisor" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("50 - parse simple widthratio without divisor\n", .{}); + + const allocator = testing.allocator; + const template = "Progresso: {% widthratio progresso 0 %}%"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Progresso: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .widthratio); + const wr = nodes[1].tag.?.body.widthratio; + try testing.expectEqualStrings("progresso", wr.value); + try testing.expectEqualStrings("0", wr.max_value); + try testing.expect(wr.divisor == null); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings("%", nodes[2].text.?.content); +} + +test "parse simple verbatim" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("51 - parse simple verbatim\n", .{}); + + const allocator = testing.allocator; + const template = "Texto {% verbatim %}{{ variável }}{% endblock %}{% endverbatim %} Texto"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + 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("Texto ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .tag); + try testing.expect(nodes[1].tag.?.kind == .verbatim); + const vb = nodes[1].tag.?.body.verbatim; + std.debug.print("{s}\n", .{vb.name.?}); + try testing.expectEqualStrings("{{ variável }}{% endblock %}", vb.content); + try testing.expect(std.mem.eql(u8, vb.name.?, "")); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Texto", nodes[2].text.?.content); +} + +test "parse verbatim nested" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("52 - parse verbatim nested\n", .{}); + + const allocator = testing.allocator; + const template = "{% verbatim baz %}Outer {% verbatim %}Inner{% endverbatim %} Outer{% endverbatim baz %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .verbatim); + const vb = nodes[0].tag.?.body.verbatim; + try testing.expectEqualStrings("Outer {% verbatim %}Inner{% endverbatim %} Outer", vb.content); + try testing.expect(std.mem.eql(u8, vb.name.?, "baz")); +} + +test "parse simple with block" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("53 - parse with block\n", .{}); + + const allocator = testing.allocator; + const template = "{% with nome=\"Lucas\" idade=30 %}Olá {{ nome }}, você tem {{ idade }} anos.{% endwith %}"; + var p = parser.Parser.init(template); + const nodes = try p.parse(allocator); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .tag); + try testing.expect(nodes[0].tag.?.kind == .with_block); + const w = nodes[0].tag.?.body.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 simple tag" { +// std.debug.print("____________________________________________________\n", .{}); +// std.debug.print("54 - parse simple tag\n", .{}); +// +// const allocator = testing.allocator; +// const template = "Antes {% minha_tag %} Depois"; +// var p = parser.Parser.init(template); +// const nodes = try p.parse(allocator); +// 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); +// }