Compare commits

...

4 commits

2 changed files with 95 additions and 8 deletions

View file

@ -3,7 +3,7 @@ const std = @import("std");
pub fn main() !void { pub fn main() !void {
try execute( try execute(
std.io.getStdOut().writer(), std.io.getStdOut().writer(),
.{ .test_tmpl = "{.x} {@context_foo}" }, .{ .test_tmpl = "{.x} {%context_foo}" },
@embedFile("./test.tmp.html"), @embedFile("./test.tmp.html"),
.{ .{
.community = .{ .name = "abcd" }, .community = .{ .name = "abcd" },
@ -15,6 +15,9 @@ pub fn main() !void {
.bar = .{ .x = "x" }, .bar = .{ .x = "x" },
.qux = false, .qux = false,
.quxx = true, .quxx = true,
.maybe_foo = @as(?[]const u8, "foo"),
.maybe_bar = @as(?[]const u8, null),
.x = "y",
}, },
.{ .{
.context_foo = "foo", .context_foo = "foo",
@ -83,9 +86,33 @@ fn executeStatement(
.@"if" => |if_stmt| { .@"if" => |if_stmt| {
const condition = evaluateExpression(if_stmt.header.condition, args, captures, context); const condition = evaluateExpression(if_stmt.header.condition, args, captures, context);
const subtemplate = if_stmt.subtemplate; const subtemplate = if_stmt.subtemplate;
if (condition) { var was_true: bool = false;
try executeTemplate(writer, templates, subtemplate, args, captures, context); if (if_stmt.header.capture) |capture| {
if (condition) |val| {
was_true = true;
try executeTemplate(
writer,
templates,
subtemplate,
args,
addCapture(captures, capture, val),
context,
);
}
} else { } else {
if (condition) {
was_true = true;
try executeTemplate(
writer,
templates,
subtemplate,
args,
captures,
context,
);
}
}
if (!was_true) {
if (if_stmt.else_branch) |branch| switch (branch) { if (if_stmt.else_branch) |branch| switch (branch) {
.@"else" => |subtmpl| try executeTemplate(writer, templates, subtmpl, args, captures, context), .@"else" => |subtmpl| try executeTemplate(writer, templates, subtmpl, args, captures, context),
.elif => |elif| try executeStatement(writer, templates, .{ .@"if" = elif.* }, args, captures, context), .elif => |elif| try executeStatement(writer, templates, .{ .@"if" = elif.* }, args, captures, context),
@ -158,6 +185,7 @@ fn evaluateExpression(
} }
fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type) type { fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type) type {
if (std.mem.eql(u8, name, "_")) return Root;
var fields = std.meta.fields(Root) ++ [_]std.builtin.Type.StructField{.{ var fields = std.meta.fields(Root) ++ [_]std.builtin.Type.StructField{.{
.name = name, .name = name,
.field_type = Val, .field_type = Val,
@ -175,6 +203,7 @@ fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type
} }
fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture(@TypeOf(root), name, @TypeOf(val)) { fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture(@TypeOf(root), name, @TypeOf(val)) {
if (comptime std.mem.eql(u8, name, "_")) return root;
var result = std.mem.zeroInit(AddCapture(@TypeOf(root), name, @TypeOf(val)), root); var result = std.mem.zeroInit(AddCapture(@TypeOf(root), name, @TypeOf(val)), root);
@field(result, name) = val; @field(result, name) = val;
return result; return result;
@ -359,6 +388,8 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken
.slash => items = items ++ [_]TemplateToken{.{ .text = "/" }}, .slash => items = items ++ [_]TemplateToken{.{ .text = "/" }},
.equals => items = items ++ [_]TemplateToken{.{ .text = "=" }}, .equals => items = items ++ [_]TemplateToken{.{ .text = "=" }},
.at => items = items ++ [_]TemplateToken{.{ .text = "@" }}, .at => items = items ++ [_]TemplateToken{.{ .text = "@" }},
.comma => items = items ++ [_]TemplateToken{.{ .text = "," }},
.percent => items = items ++ [_]TemplateToken{.{ .text = "%" }},
}; };
return items; return items;
@ -381,7 +412,7 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt
iter = names.new_iter; iter = names.new_iter;
break .{ .capture_deref = names.item }; break .{ .capture_deref = names.item };
}, },
.at => { .percent => {
const names = parseDeref(iter); const names = parseDeref(iter);
iter = names.new_iter; iter = names.new_iter;
break .{ .context_deref = names.item }; break .{ .context_deref = names.item };
@ -455,7 +486,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken
.@"if" => break .{ .end_if = {} }, .@"if" => break .{ .end_if = {} },
} }
}, },
.period, .dollar, .at => { .period, .dollar, .percent => {
iter.putBack(token); iter.putBack(token);
const expr = parseExpression(iter); const expr = parseExpression(iter);
iter = expr.new_iter; iter = expr.new_iter;
@ -551,15 +582,61 @@ fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIte
} }
} }
fn tryParseCapture(comptime tokens: ControlTokenIter) ?ParseResult(ControlTokenIter, []const []const u8) {
comptime {
var iter = tokens;
iter = skipWhitespace(iter);
if ((iter.next() orelse return null) != .pipe) return null;
var captures: []const []const u8 = &.{};
while (true) {
iter = skipWhitespace(iter);
if ((iter.next() orelse return null) != .dollar) return null;
iter = skipWhitespace(iter);
const name = switch (iter.next() orelse return null) {
.text => |text| text,
else => return null,
};
iter = skipWhitespace(iter);
captures = captures ++ &[_][]const u8{name};
switch (iter.next() orelse return null) {
.pipe => break,
.comma => {},
else => return null,
}
}
return .{
.new_iter = iter,
.item = captures,
};
}
}
fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfHeader) { fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfHeader) {
comptime { comptime {
const condition = parseExpression(tokens); const condition = parseExpression(tokens);
var iter = condition.new_iter; var iter = condition.new_iter;
const captures = tryParseCapture(iter);
if (captures) |cap| {
if (cap.item.len == 1) {
return .{
.new_iter = cap.new_iter,
.item = IfHeader{
.condition = condition.item,
.capture = cap.item[0],
},
};
} else @compileError("Only one capture allowed for if statements");
}
return .{ return .{
.new_iter = iter, .new_iter = iter,
.item = .{ .item = .{
.condition = condition.item, .condition = condition.item,
.capture = null,
}, },
}; };
} }
@ -582,7 +659,7 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [
if (wants != .period) @compileError("Unexpected token \".\""); if (wants != .period) @compileError("Unexpected token \".\"");
wants = .text; wants = .text;
}, },
else => if (wants == .period) return .{ else => if (wants == .period or fields.len == 0) return .{
.new_iter = iter, .new_iter = iter,
.item = fields, .item = fields,
} else @compileError("Unexpected token"), } else @compileError("Unexpected token"),
@ -657,6 +734,7 @@ const CallTemplate = struct {
const IfHeader = struct { const IfHeader = struct {
condition: Expression, condition: Expression,
capture: ?[]const u8,
}; };
const Statement = union(enum) { const Statement = union(enum) {
@ -707,6 +785,8 @@ const ControlToken = union(enum) {
slash: void, slash: void,
equals: void, equals: void,
at: void, at: void,
comma: void,
percent: void,
}; };
const ControlTokenIter = struct { const ControlTokenIter = struct {
@ -737,6 +817,8 @@ const ControlTokenIter = struct {
'/' => return .{ .slash = {} }, '/' => return .{ .slash = {} },
'=' => return .{ .equals = {} }, '=' => return .{ .equals = {} },
'@' => return .{ .at = {} }, '@' => return .{ .at = {} },
',' => return .{ .comma = {} },
'%' => return .{ .percent = {} },
' ', '\t', '\n', '\r' => { ' ', '\t', '\n', '\r' => {
var idx: usize = 0; var idx: usize = 0;
while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {}

View file

@ -22,9 +22,14 @@
neither neither
{=/if} {=/if}
<template>{#template test_tmpl .bar}</template> {#if .maybe_foo |$v|}{$v}{#else}null{/if}
{#if .maybe_bar |$v|}{$v}{#else}null{/if}
{#if .maybe_foo |$_|}abcd{#else}null{/if}
{@context_foo} <template>{#template test_tmpl .bar}</template>
<template>{#template test_tmpl .}</template>
{%context_foo}
</section> </section>
</body> </body>
</html> </html>