From ad513d70e21b685c65d7127aad3b4f2ff444e9c9 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 10 Dec 2022 01:21:39 -0800 Subject: [PATCH] Add switch statements --- src/template/lib.zig | 190 +++++++++++++++++++++++++++++++++++-- src/template/test.tmp.html | 16 ++++ 2 files changed, 199 insertions(+), 7 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index da0fef8..7f02a20 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub fn main() !void { - const Enum = enum { foo, bar }; + const Enum = enum { foo, bar, baz }; try execute( std.io.getStdOut().writer(), .{ .test_tmpl = "{.x} {%context_foo}" }, @@ -20,6 +20,11 @@ pub fn main() !void { .maybe_foo = @as(?[]const u8, "foo"), .maybe_bar = @as(?[]const u8, null), .snap = Enum.bar, + .crackle = union(Enum) { + foo: []const u8, + bar: []const u8, + baz: []const u8, + }{ .foo = "abcd" }, .x = "y", }, .{ @@ -122,6 +127,44 @@ fn executeStatement( }; } }, + .@"switch" => |switch_stmt| { + const expr = evaluateExpression(switch_stmt.expression, args, captures, context); + + var found = false; + inline for (switch_stmt.cases) |case| { + if (std.meta.isTag(expr, case.header.tag)) { + found = true; + if (case.header.capture) |capture| { + try executeTemplate( + writer, + templates, + case.subtemplate, + args, + addCapture(captures, capture, @field(expr, case.header.tag)), + context, + ); + } else { + try executeTemplate( + writer, + templates, + case.subtemplate, + args, + captures, + context, + ); + } + } + } else if (!found) if (switch_stmt.else_branch) |subtemplate| { + try executeTemplate( + writer, + templates, + subtemplate, + args, + captures, + context, + ); + }; + }, .call_template => |call| { const new_template = @field(templates, call.template_name); try execute( @@ -237,6 +280,7 @@ const TemplateType = enum { for_block, if_block, if_else_block, + switch_block, }; const TemplateParseResult = struct { @@ -293,6 +337,7 @@ fn parseTemplate( comptime template_type: TemplateType, ) TemplateParseResult { comptime { + @setEvalBranchQuota(tokens.len * 100); var i: usize = start; var current_text: []const u8 = ""; var items: []const TemplateItem = &.{}; @@ -336,6 +381,41 @@ fn parseTemplate( }}; i = subtemplate.new_idx; }, + .switch_header => |header| { + var cases: []const Case = &.{}; + var else_branch: ?[]const TemplateItem = null; + var last_header: CaseHeader = header.first_case; + var is_else = false; + while (true) { + const case = parseTemplate(tokens, i + 1, .switch_block); + i = case.new_idx; + + if (!is_else) { + cases = cases ++ [_]Case{.{ + .header = last_header, + .subtemplate = case.items, + }}; + } else { + else_branch = case.items; + } + switch (case.closing_block.?.block) { + .end_switch => break, + .@"else" => is_else = true, + .case_header => |case_header| last_header = case_header, + else => @compileError("Unexpected token"), + } + } + + items = items ++ [_]TemplateItem{.{ + .statement = .{ + .@"switch" = .{ + .expression = header.expression, + .cases = cases, + .else_branch = else_branch, + }, + }, + }}; + }, .end_for => if (template_type == .for_block) break cb else @@ -348,13 +428,17 @@ fn parseTemplate( break cb else @compileError("Unexpected #elif tag"), - .@"else" => if (template_type == .if_block) + .@"else" => if (template_type == .if_block or template_type == .switch_block) break cb else @compileError("Unexpected #else tag"), .call_template => |call| items = items ++ [_]TemplateItem{.{ .statement = .{ .call_template = call }, }}, + .end_switch, .case_header => if (template_type == .switch_block) + break cb + else + @compileError("Unexpected /switch tag"), } }, } @@ -575,6 +659,16 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken iter = result.new_iter; break .{ .call_template = result.item }; }, + .@"switch" => { + const result = parseSwitchHeader(iter); + iter = result.new_iter; + break .{ .switch_header = result.item }; + }, + .case => { + const result = parseCaseHeader(iter); + iter = result.new_iter; + break .{ .case_header = result.item }; + }, //else => @compileError("TODO"), } @@ -588,6 +682,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken switch (keyword) { .@"for" => break .{ .end_for = {} }, .@"if" => break .{ .end_if = {} }, + .@"switch" => break .{ .end_switch = {} }, } }, .period, .dollar, .percent => { @@ -746,6 +841,57 @@ fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter } } +fn parseCaseHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, CaseHeader) { + comptime { + var iter = skipWhitespace(tokens); + const tag = iter.next(); + expectToken(tag, .text); + + const captures = tryParseCapture(iter); + if (captures) |cap| { + if (cap.item.len == 1) { + return .{ + .new_iter = cap.new_iter, + .item = CaseHeader{ + .tag = tag.?.text, + .capture = cap.item[0], + }, + }; + } else @compileError("Only one capture allowed for case statements"); + } + + return .{ + .new_iter = iter, + .item = .{ + .tag = tag.?.text, + .capture = null, + }, + }; + } +} + +fn parseSwitchHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, SwitchHeader) { + comptime { + const condition = parseExpression(tokens); + var iter = skipWhitespace(condition.new_iter); + + const next = iter.next(); + expectToken(next, .text); + if (!std.mem.eql(u8, next.?.text, "case")) @compileError("Expected case following switch condition"); + + iter = skipWhitespace(iter); + const first = parseCaseHeader(iter); + + return .{ + .new_iter = first.new_iter, + .item = .{ + .expression = condition.item, + .first_case = first.item, + }, + }; + } +} + fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) { comptime { var iter = tokens; @@ -755,7 +901,7 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [ switch (token) { .whitespace => {}, .text => |text| { - if (wants != .text) @compileError("Unexpected token \"" ++ text ++ "\""); + if (wants == .period) break; fields = fields ++ [1][]const u8{text}; wants = .period; }, @@ -763,13 +909,15 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [ if (wants != .period) @compileError("Unexpected token \".\""); wants = .text; }, - else => if (wants == .period or fields.len == 0) return .{ - .new_iter = iter, - .item = fields, - } else @compileError("Unexpected token"), + else => if (wants == .period or fields.len == 0) break else @compileError("Unexpected token"), } _ = iter.next(); } + + return .{ + .new_iter = iter, + .item = fields, + }; } } @@ -838,6 +986,27 @@ const If = struct { }, }; +const Case = struct { + header: CaseHeader, + subtemplate: []const TemplateItem, +}; + +const SwitchHeader = struct { + expression: Expression, + first_case: CaseHeader, +}; + +const CaseHeader = struct { + tag: []const u8, + capture: ?[]const u8, +}; + +const Switch = struct { + expression: Expression, + cases: []const Case, + else_branch: ?[]const TemplateItem, +}; + const CallTemplate = struct { template_name: []const u8, args: Expression, @@ -852,6 +1021,7 @@ const Statement = union(enum) { expression: Expression, @"for": For, @"if": If, + @"switch": Switch, call_template: CallTemplate, }; @@ -865,6 +1035,9 @@ const ControlBlock = struct { @"else": void, elif_header: IfHeader, call_template: CallTemplate, + switch_header: SwitchHeader, + case_header: CaseHeader, + end_switch: void, }; block: Data, strip_before: bool, @@ -877,11 +1050,14 @@ const Keyword = enum { @"else", @"elif", @"template", + @"switch", + @"case", }; const EndKeyword = enum { @"for", @"if", + @"switch", }; const Builtin = enum { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 36ad2b4..de2620d 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -26,6 +26,22 @@ neither {=/if} + {#switch .snap case foo =} + foo + {= #case bar =} + bar + {= #else =} + other + {= /switch} + + crackle: {#switch .crackle case foo |$foo|=} + foo:{$foo} + {= #case bar |$bar|=} + bar:{$bar} + {= #else =} + other + {= /switch} + {#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if}