initial
This commit is contained in:
commit
da8c1563c6
7 changed files with 935 additions and 0 deletions
411
src/parser.zig
Normal file
411
src/parser.zig
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const NodeType = enum {
|
||||
text,
|
||||
variable,
|
||||
tag,
|
||||
if_block,
|
||||
for_block,
|
||||
};
|
||||
|
||||
pub const TextNode = struct {
|
||||
content: []const u8,
|
||||
};
|
||||
|
||||
pub const VariableNode = 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, // <--- novo
|
||||
|
||||
pub fn deinit(self: Node, allocator: std.mem.Allocator) void {
|
||||
switch (self.type) {
|
||||
.text => if (self.text) |t| allocator.free(t.content),
|
||||
.variable => if (self.variable) |v| allocator.free(v.content),
|
||||
.tag => if (self.tag) |t| {
|
||||
allocator.free(t.name);
|
||||
allocator.free(t.args);
|
||||
},
|
||||
.if_block => if (self.@"if") |ib| {
|
||||
allocator.free(ib.condition);
|
||||
// NÃO free ib.raw_open
|
||||
// NÃO free ib.raw_close
|
||||
for (ib.true_body) |n| n.deinit(allocator);
|
||||
allocator.free(ib.true_body);
|
||||
for (ib.false_body) |n| n.deinit(allocator);
|
||||
allocator.free(ib.false_body);
|
||||
},
|
||||
.for_block => if (self.@"for") |fb| {
|
||||
allocator.free(fb.loop_var);
|
||||
allocator.free(fb.iterable);
|
||||
for (fb.body) |n| n.deinit(allocator);
|
||||
allocator.free(fb.body);
|
||||
for (fb.empty_body) |n| n.deinit(allocator);
|
||||
allocator.free(fb.empty_body);
|
||||
// raw_open e raw_close são slices originais — não free
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
template: []const u8,
|
||||
pos: usize = 0,
|
||||
|
||||
pub fn init(template: []const u8) Parser {
|
||||
return .{ .template = template };
|
||||
}
|
||||
|
||||
fn advance(self: *Parser, n: usize) void {
|
||||
self.pos += n;
|
||||
if (self.pos > self.template.len) self.pos = self.template.len;
|
||||
}
|
||||
|
||||
fn peek(self: Parser, comptime n: usize) ?[]const u8 {
|
||||
if (self.pos + n > self.template.len) return null;
|
||||
return self.template[self.pos .. self.pos + n];
|
||||
}
|
||||
|
||||
fn skipWhitespace(self: *Parser) void {
|
||||
while (self.pos < self.template.len and std.ascii.isWhitespace(self.template[self.pos])) : (self.advance(1)) {}
|
||||
}
|
||||
|
||||
fn parseText(self: *Parser, allocator: std.mem.Allocator) !?Node {
|
||||
const start = self.pos;
|
||||
|
||||
while (self.pos < self.template.len) {
|
||||
if (self.peek(2)) |p| {
|
||||
if (std.mem.eql(u8, p, "{{") or std.mem.eql(u8, p, "{%")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.advance(1);
|
||||
}
|
||||
|
||||
if (self.pos == start) return null;
|
||||
|
||||
const content = try allocator.dupe(u8, self.template[start..self.pos]);
|
||||
return Node{
|
||||
.type = .text,
|
||||
.text = .{ .content = content },
|
||||
};
|
||||
}
|
||||
|
||||
fn parseVariable(self: *Parser, allocator: std.mem.Allocator) !?Node {
|
||||
if (self.peek(2)) |p| {
|
||||
if (!std.mem.eql(u8, p, "{{")) return null;
|
||||
} else return null;
|
||||
|
||||
self.advance(2);
|
||||
self.skipWhitespace();
|
||||
|
||||
const content_start = self.pos;
|
||||
while (self.pos < self.template.len) : (self.advance(1)) {
|
||||
if (self.peek(2)) |p| {
|
||||
if (std.mem.eql(u8, p, "}}")) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "}}")) {
|
||||
return error.UnclosedVariable;
|
||||
}
|
||||
|
||||
const raw_content = self.template[content_start..self.pos];
|
||||
const content = std.mem.trim(u8, raw_content, " \t\r\n");
|
||||
|
||||
const duped = try allocator.dupe(u8, content);
|
||||
self.advance(2);
|
||||
|
||||
return Node{
|
||||
.type = .variable,
|
||||
.variable = .{ .content = duped },
|
||||
};
|
||||
}
|
||||
|
||||
fn parseTag(self: *Parser, allocator: std.mem.Allocator) !?Node {
|
||||
if (self.peek(2)) |p| {
|
||||
if (!std.mem.eql(u8, p, "{%")) return null;
|
||||
} else return null;
|
||||
|
||||
const raw_start = self.pos;
|
||||
self.advance(2);
|
||||
self.skipWhitespace();
|
||||
|
||||
const content_start = self.pos;
|
||||
while (self.pos < self.template.len) : (self.advance(1)) {
|
||||
if (self.peek(2)) |p| {
|
||||
if (std.mem.eql(u8, p, "%}")) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) {
|
||||
return error.UnclosedTag;
|
||||
}
|
||||
|
||||
const raw_slice = self.template[raw_start .. self.pos + 2];
|
||||
const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n");
|
||||
|
||||
const space_idx = std.mem.indexOfScalar(u8, inner, ' ') orelse inner.len;
|
||||
const name_raw = inner[0..space_idx];
|
||||
const args_raw = if (space_idx < inner.len) std.mem.trim(u8, inner[space_idx + 1 ..], " \t\r\n") else "";
|
||||
|
||||
const name = try allocator.dupe(u8, name_raw);
|
||||
const args = try allocator.dupe(u8, args_raw);
|
||||
|
||||
self.advance(2);
|
||||
|
||||
return Node{
|
||||
.type = .tag,
|
||||
.tag = .{
|
||||
.name = name,
|
||||
.args = args,
|
||||
.raw = raw_slice, // slice original, sem dupe
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parseIfBlock(self: *Parser, allocator: std.mem.Allocator, condition: []const u8, raw_open: []const u8) !Node {
|
||||
var true_body = std.ArrayList(Node){};
|
||||
defer true_body.deinit(allocator);
|
||||
var false_body = std.ArrayList(Node){};
|
||||
defer false_body.deinit(allocator);
|
||||
|
||||
var current_body = &true_body;
|
||||
var depth: usize = 1;
|
||||
|
||||
while (self.pos < self.template.len and depth > 0) {
|
||||
if (try self.parseText(allocator)) |node| {
|
||||
try current_body.append(allocator, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (try self.parseVariable(allocator)) |node| {
|
||||
try current_body.append(allocator, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (try self.parseTag(allocator)) |tag_node| {
|
||||
const tag_name = tag_node.tag.?.name;
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "if")) {
|
||||
depth += 1;
|
||||
try current_body.append(allocator, tag_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "endif")) {
|
||||
depth -= 1;
|
||||
const raw_close = tag_node.tag.?.raw;
|
||||
|
||||
// Libera name e args da tag endif
|
||||
allocator.free(tag_node.tag.?.name);
|
||||
allocator.free(tag_node.tag.?.args);
|
||||
|
||||
if (depth == 0) {
|
||||
return Node{
|
||||
.type = .if_block,
|
||||
.@"if" = .{
|
||||
.condition = condition,
|
||||
.true_body = try true_body.toOwnedSlice(allocator),
|
||||
.false_body = try false_body.toOwnedSlice(allocator),
|
||||
.raw_open = raw_open,
|
||||
.raw_close = raw_close,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Se depth > 0, é endif aninhado — adiciona como tag normal
|
||||
try current_body.append(allocator, tag_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "else") and depth == 1) {
|
||||
current_body = &false_body;
|
||||
allocator.free(tag_node.tag.?.name);
|
||||
allocator.free(tag_node.tag.?.args);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Qualquer outra tag
|
||||
try current_body.append(allocator, tag_node);
|
||||
} else {
|
||||
self.advance(1);
|
||||
}
|
||||
}
|
||||
|
||||
return error.UnclosedBlock;
|
||||
}
|
||||
|
||||
fn parseForBlock(self: *Parser, allocator: std.mem.Allocator, loop_var: []const u8, iterable: []const u8, raw_open: []const u8) !Node {
|
||||
var body = std.ArrayList(Node){};
|
||||
defer body.deinit(allocator);
|
||||
var empty_body = std.ArrayList(Node){};
|
||||
defer empty_body.deinit(allocator);
|
||||
|
||||
var current_body = &body;
|
||||
var depth: usize = 1;
|
||||
|
||||
while (self.pos < self.template.len and depth > 0) {
|
||||
if (try self.parseText(allocator)) |node| {
|
||||
try current_body.append(allocator, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (try self.parseVariable(allocator)) |node| {
|
||||
try current_body.append(allocator, node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (try self.parseTag(allocator)) |tag_node| {
|
||||
const tag_name = tag_node.tag.?.name;
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "for")) {
|
||||
depth += 1;
|
||||
try current_body.append(allocator, tag_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "endfor")) {
|
||||
depth -= 1;
|
||||
const raw_close = tag_node.tag.?.raw;
|
||||
|
||||
if (depth == 0) {
|
||||
// Libera name e args — essa é a tag de fechamento final
|
||||
allocator.free(tag_node.tag.?.name);
|
||||
allocator.free(tag_node.tag.?.args);
|
||||
|
||||
return Node{
|
||||
.type = .for_block,
|
||||
.@"for" = .{
|
||||
.loop_var = loop_var,
|
||||
.iterable = iterable,
|
||||
.body = try body.toOwnedSlice(allocator),
|
||||
.empty_body = try empty_body.toOwnedSlice(allocator),
|
||||
.raw_open = raw_open,
|
||||
.raw_close = raw_close,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// depth > 0: endfor aninhado — adiciona como tag normal
|
||||
try current_body.append(allocator, tag_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "empty") and depth == 1) {
|
||||
current_body = &empty_body;
|
||||
allocator.free(tag_node.tag.?.name);
|
||||
allocator.free(tag_node.tag.?.args);
|
||||
continue;
|
||||
}
|
||||
|
||||
try current_body.append(allocator, tag_node);
|
||||
} else {
|
||||
self.advance(1);
|
||||
}
|
||||
}
|
||||
|
||||
return error.UnclosedBlock;
|
||||
}
|
||||
|
||||
pub fn parse(self: *Parser, allocator: std.mem.Allocator) ![]Node {
|
||||
var list = std.ArrayList(Node){};
|
||||
defer list.deinit(allocator);
|
||||
|
||||
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, "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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return try list.toOwnedSlice(allocator);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parse(allocator: std.mem.Allocator, template: []const u8) ![]Node {
|
||||
var p = Parser.init(template);
|
||||
return try p.parse(allocator);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue