Add optional unwrap syntax

This commit is contained in:
jaina heartles 2022-12-13 01:33:22 -08:00
parent a230a26151
commit bf0d2f6642
2 changed files with 40 additions and 15 deletions

View file

@ -40,7 +40,7 @@ pub fn execute(
args: anytype, args: anytype,
context: anytype, context: anytype,
) !void { ) !void {
@setEvalBranchQuota(@intCast(u32, template.len * 8)); @setEvalBranchQuota(@intCast(u32, template.len * 12));
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);
@ -209,20 +209,30 @@ fn print(writer: anytype, arg: anytype) !void {
try std.fmt.format(writer, "{}", .{arg}); 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; if (names.len == 0) return T;
// Compiler segfaults when I use std.meta to get this info so we search it manually // Compiler segfaults when I use std.meta to get this info so we search it manually
const F = switch (names[0]) {
.field => |name| blk: {
const field = for (@typeInfo(T).Struct.fields) |f| { const field = for (@typeInfo(T).Struct.fields) |f| {
if (std.mem.eql(u8, f.name, names[0])) break f; if (std.mem.eql(u8, f.name, name)) break f;
} else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T)); } 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; 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( fn EvaluateExpression(
@ -524,6 +534,7 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken
.open_paren => items = items ++ [_]TemplateToken{.{ .text = "(" }}, .open_paren => items = items ++ [_]TemplateToken{.{ .text = "(" }},
.close_paren => items = items ++ [_]TemplateToken{.{ .text = ")" }}, .close_paren => items = items ++ [_]TemplateToken{.{ .text = ")" }},
.double_quote => items = items ++ [_]TemplateToken{.{ .text = "\"" }}, .double_quote => items = items ++ [_]TemplateToken{.{ .text = "\"" }},
.question_mark => items = items ++ [_]TemplateToken{.{ .text = "?" }},
}; };
return items; 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 { comptime {
var iter = tokens; var iter = tokens;
var fields: []const []const u8 = &.{}; var fields: []const DerefDecl = &.{};
var wants = .text; var wants = .text;
while (iter.peek()) |token| { while (iter.peek()) |token| {
switch (token) { switch (token) {
.whitespace => {}, .whitespace => {},
.text => |text| { .text => |text| {
if (wants == .period) break; if (wants == .period) break;
fields = fields ++ [1][]const u8{text}; fields = fields ++ [1]DerefDecl{.{ .field = text }};
wants = .period; wants = .period;
}, },
.period => { .period => {
if (wants != .period) @compileError("Unexpected token \".\""); if (wants != .period) @compileError("Unexpected token \".\"");
wants = .text; 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"), else => if (wants == .period or fields.len == 0) break else @compileError("Unexpected token"),
} }
_ = iter.next(); _ = iter.next();
@ -993,6 +1009,7 @@ fn parseFormat(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter,
.percent => fmt_str = fmt_str ++ "%", .percent => fmt_str = fmt_str ++ "%",
.open_paren => fmt_str = fmt_str ++ "(", .open_paren => fmt_str = fmt_str ++ "(",
.close_paren => fmt_str = fmt_str ++ ")", .close_paren => fmt_str = fmt_str ++ ")",
.question_mark => fmt_str = fmt_str ++ "?",
.double_quote => break, .double_quote => break,
}; };
@ -1020,15 +1037,20 @@ const TemplateItem = union(enum) {
statement: Statement, statement: Statement,
}; };
const DerefDecl = union(enum) {
field: []const u8,
optional_unwrap: void,
};
const EqualsExpr = struct { const EqualsExpr = struct {
lhs: Expression, lhs: Expression,
rhs: Expression, rhs: Expression,
}; };
const Expression = union(enum) { const Expression = union(enum) {
arg_deref: []const []const u8, arg_deref: []const DerefDecl,
capture_deref: []const []const u8, capture_deref: []const DerefDecl,
context_deref: []const []const u8, context_deref: []const DerefDecl,
equals: *const EqualsExpr, equals: *const EqualsExpr,
builtin: *const BuiltinCall, builtin: *const BuiltinCall,
}; };
@ -1162,6 +1184,7 @@ const ControlToken = union(enum) {
open_paren: void, open_paren: void,
close_paren: void, close_paren: void,
double_quote: void, double_quote: void,
question_mark: void,
}; };
const ControlTokenIter = struct { const ControlTokenIter = struct {
@ -1197,6 +1220,7 @@ const ControlTokenIter = struct {
'(' => return .{ .open_paren = {} }, '(' => return .{ .open_paren = {} },
')' => return .{ .close_paren = {} }, ')' => return .{ .close_paren = {} },
'"' => return .{ .double_quote = {} }, '"' => return .{ .double_quote = {} },
'?' => return .{ .question_mark = {} },
' ', '\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) {}
@ -1208,7 +1232,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] };

View file

@ -47,6 +47,7 @@
{#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$v|}{$v}{#else}null{/if}
{#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if}
{#if .maybe_foo |$_|}abcd{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if}
{.maybe_foo.?}
<template>{#template test_tmpl .bar}</template> <template>{#template test_tmpl .bar}</template>
<template>{#template test_tmpl .}</template> <template>{#template test_tmpl .}</template>