diff --git a/src/template/lib.zig b/src/template/lib.zig index 4a82a1b..2a99654 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -40,7 +40,7 @@ pub fn execute( args: anytype, context: anytype, ) !void { - @setEvalBranchQuota(@intCast(u32, template.len * 8)); + @setEvalBranchQuota(@intCast(u32, template.len * 12)); const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template }); const tmpl = comptime parseTemplate(tokens, 0, .root); @@ -209,20 +209,30 @@ fn print(writer: anytype, arg: anytype) !void { try std.fmt.format(writer, "{}", .{arg}); } -fn Deref(comptime T: type, comptime names: []const []const u8) type { +fn Deref(comptime T: type, comptime names: []const DerefDecl) type { if (names.len == 0) return T; // Compiler segfaults when I use std.meta to get this info so we search it manually - const field = for (@typeInfo(T).Struct.fields) |f| { - if (std.mem.eql(u8, f.name, names[0])) break f; - } else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T)); + const F = switch (names[0]) { + .field => |name| blk: { + const field = for (@typeInfo(T).Struct.fields) |f| { + if (std.mem.eql(u8, f.name, name)) break f; + } else @compileError("Unknown field " ++ name ++ " in type " ++ @typeName(T)); + break :blk field.field_type; + }, + .optional_unwrap => std.meta.Child(T), + }; - return Deref(field.field_type, names[1..]); + return Deref(F, names[1..]); } -fn deref(arg: anytype, comptime names: []const []const u8) Deref(@TypeOf(arg), names) { +fn deref(arg: anytype, comptime names: []const DerefDecl) Deref(@TypeOf(arg), names) { if (names.len == 0) return arg; - return deref(@field(arg, names[0]), names[1..]); + + switch (names[0]) { + .field => |name| return deref(@field(arg, name), names[1..]), + .optional_unwrap => return arg.?, + } } fn EvaluateExpression( @@ -524,6 +534,7 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken .open_paren => items = items ++ [_]TemplateToken{.{ .text = "(" }}, .close_paren => items = items ++ [_]TemplateToken{.{ .text = ")" }}, .double_quote => items = items ++ [_]TemplateToken{.{ .text = "\"" }}, + .question_mark => items = items ++ [_]TemplateToken{.{ .text = "?" }}, }; return items; @@ -922,23 +933,28 @@ fn parseSwitchHeader(comptime tokens: ControlTokenIter) ParseResult(ControlToken } } -fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) { +fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const DerefDecl) { comptime { var iter = tokens; - var fields: []const []const u8 = &.{}; + var fields: []const DerefDecl = &.{}; var wants = .text; while (iter.peek()) |token| { switch (token) { .whitespace => {}, .text => |text| { if (wants == .period) break; - fields = fields ++ [1][]const u8{text}; + fields = fields ++ [1]DerefDecl{.{ .field = text }}; wants = .period; }, .period => { if (wants != .period) @compileError("Unexpected token \".\""); wants = .text; }, + .question_mark => { + if (wants == .period) break; + fields = fields ++ [1]DerefDecl{.{ .optional_unwrap = {} }}; + wants = .period; + }, else => if (wants == .period or fields.len == 0) break else @compileError("Unexpected token"), } _ = iter.next(); @@ -993,6 +1009,7 @@ fn parseFormat(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, .percent => fmt_str = fmt_str ++ "%", .open_paren => fmt_str = fmt_str ++ "(", .close_paren => fmt_str = fmt_str ++ ")", + .question_mark => fmt_str = fmt_str ++ "?", .double_quote => break, }; @@ -1020,15 +1037,20 @@ const TemplateItem = union(enum) { statement: Statement, }; +const DerefDecl = union(enum) { + field: []const u8, + optional_unwrap: void, +}; + const EqualsExpr = struct { lhs: Expression, rhs: Expression, }; const Expression = union(enum) { - arg_deref: []const []const u8, - capture_deref: []const []const u8, - context_deref: []const []const u8, + arg_deref: []const DerefDecl, + capture_deref: []const DerefDecl, + context_deref: []const DerefDecl, equals: *const EqualsExpr, builtin: *const BuiltinCall, }; @@ -1162,6 +1184,7 @@ const ControlToken = union(enum) { open_paren: void, close_paren: void, double_quote: void, + question_mark: void, }; const ControlTokenIter = struct { @@ -1197,6 +1220,7 @@ const ControlTokenIter = struct { '(' => return .{ .open_paren = {} }, ')' => return .{ .close_paren = {} }, '"' => return .{ .double_quote = {} }, + '?' => return .{ .question_mark = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} @@ -1208,7 +1232,7 @@ const ControlTokenIter = struct { }, else => { 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; return .{ .text = remaining[0..idx] }; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 475581b..0dcc6a0 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -47,6 +47,7 @@ {#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if} + {.maybe_foo.?}