(#64) Add implicit context

Closes #64
This commit is contained in:
jaina heartles 2022-12-07 23:47:07 -08:00
parent 8c0548fc74
commit 86e203730c
3 changed files with 71 additions and 25 deletions

View file

@ -227,7 +227,7 @@ pub const Response = struct {
defer stream.close(); defer stream.close();
const writer = stream.writer(); const writer = stream.writer();
try @import("template").execute(writer, .{}, templ, data); try @import("template").execute(writer, .{}, templ, data, .{});
try stream.finish(); try stream.finish();
} }

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}" }, .{ .test_tmpl = "{.x} {@context_foo}" },
@embedFile("./test.tmp.html"), @embedFile("./test.tmp.html"),
.{ .{
.community = .{ .name = "abcd" }, .community = .{ .name = "abcd" },
@ -16,34 +16,57 @@ pub fn main() !void {
.qux = false, .qux = false,
.quxx = true, .quxx = true,
}, },
.{
.context_foo = "foo",
},
); );
} }
pub fn execute(writer: anytype, comptime other_templates: anytype, comptime template: []const u8, args: anytype) !void { pub fn execute(
writer: anytype,
comptime other_templates: anytype,
comptime template: []const u8,
args: anytype,
context: anytype,
) !void {
@setEvalBranchQuota(@intCast(u32, template.len * 8)); @setEvalBranchQuota(@intCast(u32, template.len * 8));
const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template }); const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template });
const tmpl = comptime parseTemplate(tokens, 0, .root); const tmpl = comptime parseTemplate(tokens, 0, .root);
try executeTemplate(writer, other_templates, tmpl.items, args, .{}); try executeTemplate(writer, other_templates, tmpl.items, args, .{}, context);
} }
fn executeTemplate(writer: anytype, comptime templates: anytype, comptime items: []const TemplateItem, args: anytype, captures: anytype) !void { fn executeTemplate(
writer: anytype,
comptime templates: anytype,
comptime items: []const TemplateItem,
args: anytype,
captures: anytype,
context: anytype,
) !void {
inline for (items) |it| { inline for (items) |it| {
switch (it) { switch (it) {
.text => |text| try writer.writeAll(text), .text => |text| try writer.writeAll(text),
.statement => |stmt| try executeStatement(writer, templates, stmt, args, captures), .statement => |stmt| try executeStatement(writer, templates, stmt, args, captures, context),
} }
} }
} }
fn executeStatement(writer: anytype, comptime templates: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { fn executeStatement(
writer: anytype,
comptime templates: anytype,
comptime stmt: Statement,
args: anytype,
captures: anytype,
context: anytype,
) !void {
switch (stmt) { switch (stmt) {
.expression => |expr| { .expression => |expr| {
const val = evaluateExpression(expr, args, captures); const val = evaluateExpression(expr, args, captures, context);
try print(writer, val); try print(writer, val);
}, },
.@"for" => |loop| { .@"for" => |loop| {
const iterable = evaluateExpression(loop.header.iterable, args, captures); const iterable = evaluateExpression(loop.header.iterable, args, captures, context);
const subtemplate = loop.subtemplate; const subtemplate = loop.subtemplate;
//std.log.debug("{any}", .{subtemplate}); //std.log.debug("{any}", .{subtemplate});
for (iterable) |v| { for (iterable) |v| {
@ -53,25 +76,31 @@ fn executeStatement(writer: anytype, comptime templates: anytype, comptime stmt:
subtemplate, subtemplate,
args, args,
addCapture(captures, loop.header.capture, v), addCapture(captures, loop.header.capture, v),
context,
); );
} }
}, },
.@"if" => |if_stmt| { .@"if" => |if_stmt| {
const condition = evaluateExpression(if_stmt.header.condition, args, captures); const condition = evaluateExpression(if_stmt.header.condition, args, captures, context);
const subtemplate = if_stmt.subtemplate; const subtemplate = if_stmt.subtemplate;
if (condition) { if (condition) {
try executeTemplate(writer, templates, subtemplate, args, captures); try executeTemplate(writer, templates, subtemplate, args, captures, context);
} else { } else {
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), .@"else" => |subtmpl| try executeTemplate(writer, templates, subtmpl, args, captures, context),
.elif => |elif| try executeStatement(writer, templates, .{ .@"if" = elif.* }, args, captures), .elif => |elif| try executeStatement(writer, templates, .{ .@"if" = elif.* }, args, captures, context),
}; };
} }
}, },
.call_template => |call| { .call_template => |call| {
const new_template = @field(templates, call.template_name); const new_template = @field(templates, call.template_name);
try execute(writer, templates, new_template, evaluateExpression(call.args, args, captures)); try execute(
//std.log.debug("calling template {s} with arg {any}", .{ call.template_name, call.args }); writer,
templates,
new_template,
evaluateExpression(call.args, args, captures, context),
context,
);
}, },
//else => @compileError("TODO"), //else => @compileError("TODO"),
} }
@ -102,10 +131,16 @@ fn deref(arg: anytype, comptime names: []const []const u8) Deref(@TypeOf(arg), n
return deref(@field(arg, names[0]), names[1..]); return deref(@field(arg, names[0]), names[1..]);
} }
fn EvaluateExpression(comptime expression: Expression, comptime Args: type, comptime Captures: type) type { fn EvaluateExpression(
comptime expression: Expression,
comptime Args: type,
comptime Captures: type,
comptime Context: type,
) type {
return switch (expression) { return switch (expression) {
.arg_deref => |names| Deref(Args, names), .arg_deref => |names| Deref(Args, names),
.capture_deref => |names| Deref(Captures, names), .capture_deref => |names| Deref(Captures, names),
.context_deref => |names| Deref(Context, names),
}; };
} }
@ -113,10 +148,12 @@ fn evaluateExpression(
comptime expression: Expression, comptime expression: Expression,
args: anytype, args: anytype,
captures: anytype, captures: anytype,
) EvaluateExpression(expression, @TypeOf(args), @TypeOf(captures)) { context: anytype,
) EvaluateExpression(expression, @TypeOf(args), @TypeOf(captures), @TypeOf(context)) {
return switch (expression) { return switch (expression) {
.arg_deref => |names| deref(args, names), .arg_deref => |names| deref(args, names),
.capture_deref => |names| deref(captures, names), .capture_deref => |names| deref(captures, names),
.context_deref => |names| deref(context, names),
}; };
} }
@ -263,11 +300,9 @@ fn parseTemplate(
break cb break cb
else else
@compileError("Unexpected #else tag"), @compileError("Unexpected #else tag"),
.call_template => |call| { .call_template => |call| items = items ++ [_]TemplateItem{.{
items = items ++ [_]TemplateItem{.{ .statement = .{ .call_template = call },
.statement = .{ .call_template = call }, }},
}};
},
} }
}, },
} }
@ -323,6 +358,7 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken
.dollar => items = items ++ [_]TemplateToken{.{ .text = "$" }}, .dollar => items = items ++ [_]TemplateToken{.{ .text = "$" }},
.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 = "@" }},
}; };
return items; return items;
@ -345,6 +381,11 @@ 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 => {
const names = parseDeref(iter);
iter = names.new_iter;
break .{ .context_deref = names.item };
},
else => @compileError("TODO"), else => @compileError("TODO"),
}; };
@ -414,7 +455,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken
.@"if" => break .{ .end_if = {} }, .@"if" => break .{ .end_if = {} },
} }
}, },
.period, .dollar => { .period, .dollar, .at => {
iter.putBack(token); iter.putBack(token);
const expr = parseExpression(iter); const expr = parseExpression(iter);
iter = expr.new_iter; iter = expr.new_iter;
@ -587,6 +628,7 @@ const TemplateItem = union(enum) {
const Expression = union(enum) { const Expression = union(enum) {
arg_deref: []const []const u8, arg_deref: []const []const u8,
capture_deref: []const []const u8, capture_deref: []const []const u8,
context_deref: []const []const u8,
}; };
const For = struct { const For = struct {
@ -664,6 +706,7 @@ const ControlToken = union(enum) {
dollar: void, dollar: void,
slash: void, slash: void,
equals: void, equals: void,
at: void,
}; };
const ControlTokenIter = struct { const ControlTokenIter = struct {
@ -693,6 +736,7 @@ const ControlTokenIter = struct {
'$' => return .{ .dollar = {} }, '$' => return .{ .dollar = {} },
'/' => return .{ .slash = {} }, '/' => return .{ .slash = {} },
'=' => return .{ .equals = {} }, '=' => return .{ .equals = {} },
'@' => return .{ .at = {} },
' ', '\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) {}
@ -704,7 +748,7 @@ const ControlTokenIter = struct {
}, },
else => { else => {
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) {}
self.start += idx - 1; self.start += idx - 1;
return .{ .text = remaining[0..idx] }; return .{ .text = remaining[0..idx] };
@ -728,7 +772,7 @@ test "template" {
const testCase = struct { const testCase = struct {
fn case(comptime tmpl: []const u8, args: anytype, expected: []const u8) !void { fn case(comptime tmpl: []const u8, args: anytype, expected: []const u8) !void {
var stream = std.io.changeDetectionStream(expected, std.io.null_writer); var stream = std.io.changeDetectionStream(expected, std.io.null_writer);
try execute(stream.writer(), tmpl, args); try execute(stream.writer(), .{}, tmpl, args, .{});
try std.testing.expect(!stream.changeDetected()); try std.testing.expect(!stream.changeDetected());
} }
}.case; }.case;

View file

@ -23,6 +23,8 @@
{=/if} {=/if}
<template>{#template test_tmpl .bar}</template> <template>{#template test_tmpl .bar}</template>
{@context_foo}
</section> </section>
</body> </body>
</html> </html>