From b0864b29d86846de08a0a07d57b3a9a768d46a2f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 7 Dec 2022 17:10:53 -0800 Subject: [PATCH 1/4] Return closing block from parseTemplate --- src/template/lib.zig | 33 +++++++++++++++++++++------------ src/template/test.tmp.html | 3 +-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index f1e141d..68ee360 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -18,7 +18,7 @@ pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !v const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template }); const tmpl = comptime parseTemplate(tokens, 0, .root); - try executeTemplate(writer, tmpl.item, args, .{}); + try executeTemplate(writer, tmpl.items, args, .{}); } fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: anytype, captures: anytype) !void { @@ -130,17 +130,23 @@ const TemplateType = enum { if_block, }; +const TemplateParseResult = struct { + new_idx: usize, + items: []const TemplateItem, + closing_block: ?ControlBlock, +}; + fn parseTemplate( comptime tokens: []const TemplateToken, comptime start: usize, comptime template_type: TemplateType, -) ParseResult(usize, []const TemplateItem) { +) TemplateParseResult { comptime { var i: usize = start; var current_text: []const u8 = ""; var items: []const TemplateItem = &.{}; - while (i < tokens.len) : (i += 1) { + const closing_block: ?ControlBlock = while (i < tokens.len) : (i += 1) { switch (tokens[i]) { .text => |text| current_text = current_text ++ text, .whitespace => |wsp| { @@ -163,12 +169,12 @@ fn parseTemplate( items = items ++ [_]TemplateItem{.{ .statement = .{ .@"if" = .{ - .subtemplate = subtemplate.item, + .subtemplate = subtemplate.items, .header = header, }, }, }}; - i = subtemplate.new_iter; + i = subtemplate.new_idx; }, .for_header => |header| { if (i != tokens.len - 1 and tokens[i + 1] == .whitespace and cb.strip_after) i += 1; @@ -176,19 +182,19 @@ fn parseTemplate( items = items ++ [_]TemplateItem{.{ .statement = .{ .@"for" = .{ - .subtemplate = subtemplate.item, + .subtemplate = subtemplate.items, .header = header, }, }, }}; - i = subtemplate.new_iter; + i = subtemplate.new_idx; }, .end_for => if (template_type == .for_block) - break + break cb else @compileError("Unexpected /for tag"), .end_if => if (template_type == .if_block) - break + break cb else @compileError("Unexpected /if tag"), } @@ -200,13 +206,16 @@ fn parseTemplate( } }, } - } else if (template_type != .root) @compileError("End tag not found"); + } else null; if (current_text.len != 0) items = items ++ [_]TemplateItem{.{ .text = current_text }}; + if (template_type != .root and closing_block == null) @compileError("End tag not found"); + return .{ - .new_iter = i, - .item = items, + .new_idx = i, + .items = items, + .closing_block = closing_block, }; } } diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 72435f4..772be18 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,8 +14,7 @@ {$b}: {= /for =} {= /for} - {#if .qux}qux!{/if=} - {#if .quxx}quxx!{/if} + {#if .qux}qux!{#elif .quxx}quxx!{/if=} From bb297a4f6e751bb0c8caec215260a2004d7f8260 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 7 Dec 2022 17:26:36 -0800 Subject: [PATCH 2/4] Parse else blocks --- src/template/lib.zig | 17 +++++++++++++++-- src/template/test.tmp.html | 3 ++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 68ee360..ec9f130 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -128,6 +128,7 @@ const TemplateType = enum { root, for_block, if_block, + if_else_block, }; const TemplateParseResult = struct { @@ -166,6 +167,12 @@ fn parseTemplate( .if_header => |header| { if (i != tokens.len - 1 and tokens[i + 1] == .whitespace and cb.strip_after) i += 1; const subtemplate = parseTemplate(tokens, i + 1, .if_block); + i = subtemplate.new_idx; + const else_subtemplate: ?TemplateParseResult = if (subtemplate.closing_block.?.block == .@"else") + parseTemplate(tokens, i + 1, .if_else_block) + else + null; + if (else_subtemplate) |sub| i = sub.new_idx; items = items ++ [_]TemplateItem{.{ .statement = .{ .@"if" = .{ @@ -174,7 +181,6 @@ fn parseTemplate( }, }, }}; - i = subtemplate.new_idx; }, .for_header => |header| { if (i != tokens.len - 1 and tokens[i + 1] == .whitespace and cb.strip_after) i += 1; @@ -193,10 +199,14 @@ fn parseTemplate( break cb else @compileError("Unexpected /for tag"), - .end_if => if (template_type == .if_block) + .end_if => if (template_type == .if_block or template_type == .if_else_block) break cb else @compileError("Unexpected /if tag"), + .@"else" => if (template_type == .if_block) + break cb + else + @compileError("Unexpected #else tag"), } if (i != tokens.len - 1 and tokens[i] == .control_block) { @@ -323,6 +333,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken iter = result.new_iter; break .{ .if_header = result.item }; }, + .@"else" => break .{ .@"else" = {} }, //else => @compileError("TODO"), } @@ -524,6 +535,7 @@ const ControlBlock = struct { end_for: void, if_header: IfHeader, end_if: void, + @"else": void, }; block: Data, strip_before: bool, @@ -533,6 +545,7 @@ const ControlBlock = struct { const Keyword = enum { @"for", @"if", + @"else", }; const EndKeyword = enum { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 772be18..56e41a5 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,7 +14,8 @@ {$b}: {= /for =} {= /for} - {#if .qux}qux!{#elif .quxx}quxx!{/if=} + {#if .qux}qux!{#else}!qux!{/if=} + {#if .quxx}quxx!{#else}!quxx!{/if} From f8a799abfb63ec577ce9f59695a817db1b1e6d49 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 7 Dec 2022 17:30:01 -0800 Subject: [PATCH 3/4] Execute else branches --- src/template/lib.zig | 9 ++++++++- src/template/test.tmp.html | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index ec9f130..e64289c 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -52,7 +52,11 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca .@"if" => |if_stmt| { const condition = evaluateExpression(if_stmt.header.condition, args, captures); const subtemplate = if_stmt.subtemplate; - if (condition) try executeTemplate(writer, subtemplate, args, captures); + if (condition) { + try executeTemplate(writer, subtemplate, args, captures); + } else { + if (if_stmt.else_branch) |branch| try executeTemplate(writer, branch, args, captures); + } }, //else => @compileError("TODO"), } @@ -178,6 +182,8 @@ fn parseTemplate( .@"if" = .{ .subtemplate = subtemplate.items, .header = header, + + .else_branch = if (else_subtemplate) |sub| sub.items else null, }, }, }}; @@ -516,6 +522,7 @@ const ForHeader = struct { const If = struct { subtemplate: []const TemplateItem, header: IfHeader, + else_branch: ?[]const TemplateItem, }; const IfHeader = struct { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 56e41a5..6d62925 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,8 +14,8 @@ {$b}: {= /for =} {= /for} - {#if .qux}qux!{#else}!qux!{/if=} - {#if .quxx}quxx!{#else}!quxx!{/if} + {#if .qux}qux{#else}!qux{/if} + {#if .quxx}quxx{#else}!quxx{/if} From 9dabc237e0cf6e08c97d40fe037a32149bf77710 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 7 Dec 2022 22:47:51 -0800 Subject: [PATCH 4/4] (#62) Add elif support to template engine Closes: #62 --- src/template/lib.zig | 110 +++++++++++++++++++++++++++---------- src/template/test.tmp.html | 9 ++- 2 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index e64289c..ff4ea23 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -8,8 +8,8 @@ pub fn main() !void { &.{ "5", "4", "3", "2", "1" }, &.{ "5", "4", "3", "2", "1" }, }, - .qux = true, - .quxx = false, + .qux = false, + .quxx = true, }); } @@ -55,7 +55,10 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca if (condition) { try executeTemplate(writer, subtemplate, args, captures); } else { - if (if_stmt.else_branch) |branch| try executeTemplate(writer, branch, args, captures); + if (if_stmt.else_branch) |branch| switch (branch) { + .@"else" => |subtmpl| try executeTemplate(writer, subtmpl, args, captures), + .elif => |elif| try executeStatement(writer, .{ .@"if" = elif.* }, args, captures), + }; } }, //else => @compileError("TODO"), @@ -141,6 +144,48 @@ const TemplateParseResult = struct { closing_block: ?ControlBlock, }; +fn parseIfBlock( + comptime header: IfHeader, + comptime tokens: []const TemplateToken, + comptime start: usize, +) ParseResult(usize, If) { + const subtemplate = parseTemplate(tokens, start, .if_block); + + switch (subtemplate.closing_block.?.block) { + .end_if => return .{ + .new_iter = subtemplate.new_idx, + .item = If{ + .header = header, + .subtemplate = subtemplate.items, + .else_branch = null, + }, + }, + .@"else" => { + const else_subtemplate = parseTemplate(tokens, subtemplate.new_idx + 1, .if_else_block); + return .{ + .new_iter = else_subtemplate.new_idx, + .item = If{ + .header = header, + .subtemplate = subtemplate.items, + .else_branch = .{ .@"else" = else_subtemplate.items }, + }, + }; + }, + .elif_header => |elif_header| { + const else_if = parseIfBlock(elif_header, tokens, subtemplate.new_idx + 1); + return .{ + .new_iter = else_if.new_iter, + .item = If{ + .header = header, + .subtemplate = subtemplate.items, + .else_branch = .{ .elif = &else_if.item }, + }, + }; + }, + else => unreachable, + } +} + fn parseTemplate( comptime tokens: []const TemplateToken, comptime start: usize, @@ -155,9 +200,12 @@ fn parseTemplate( switch (tokens[i]) { .text => |text| current_text = current_text ++ text, .whitespace => |wsp| { - if (i != tokens.len - 1 and tokens[i + 1] == .control_block) - if (tokens[i + 1].control_block.strip_before) - continue; + if (i != tokens.len - 1 and tokens[i + 1] == .control_block) { + if (tokens[i + 1].control_block.strip_before) continue; + } + if (i != 0 and tokens[i - 1] == .control_block) { + if (tokens[i - 1].control_block.strip_after) continue; + } current_text = current_text ++ wsp; }, .control_block => |cb| { @@ -169,27 +217,13 @@ fn parseTemplate( switch (cb.block) { .expression => |expr| items = items ++ [_]TemplateItem{.{ .statement = .{ .expression = expr } }}, .if_header => |header| { - if (i != tokens.len - 1 and tokens[i + 1] == .whitespace and cb.strip_after) i += 1; - const subtemplate = parseTemplate(tokens, i + 1, .if_block); - i = subtemplate.new_idx; - const else_subtemplate: ?TemplateParseResult = if (subtemplate.closing_block.?.block == .@"else") - parseTemplate(tokens, i + 1, .if_else_block) - else - null; - if (else_subtemplate) |sub| i = sub.new_idx; + const parsed = parseIfBlock(header, tokens, i + 1); + i = parsed.new_iter; items = items ++ [_]TemplateItem{.{ - .statement = .{ - .@"if" = .{ - .subtemplate = subtemplate.items, - .header = header, - - .else_branch = if (else_subtemplate) |sub| sub.items else null, - }, - }, + .statement = .{ .@"if" = parsed.item }, }}; }, .for_header => |header| { - if (i != tokens.len - 1 and tokens[i + 1] == .whitespace and cb.strip_after) i += 1; const subtemplate = parseTemplate(tokens, i + 1, .for_block); items = items ++ [_]TemplateItem{.{ .statement = .{ @@ -209,17 +243,15 @@ fn parseTemplate( break cb else @compileError("Unexpected /if tag"), + .elif_header => if (template_type == .if_block) + break cb + else + @compileError("Unexpected #elif tag"), .@"else" => if (template_type == .if_block) break cb else @compileError("Unexpected #else tag"), } - - if (i != tokens.len - 1 and tokens[i] == .control_block) { - if (tokens[i].control_block.strip_after and tokens[i + 1] == .whitespace) { - i += 1; - } - } }, } } else null; @@ -340,6 +372,11 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken break .{ .if_header = result.item }; }, .@"else" => break .{ .@"else" = {} }, + .@"elif" => { + const result = parseIfHeader(iter); + iter = result.new_iter; + break .{ .elif_header = result.item }; + }, //else => @compileError("TODO"), } @@ -522,7 +559,10 @@ const ForHeader = struct { const If = struct { subtemplate: []const TemplateItem, header: IfHeader, - else_branch: ?[]const TemplateItem, + else_branch: ?union(enum) { + @"else": []const TemplateItem, + elif: *const If, + }, }; const IfHeader = struct { @@ -543,6 +583,7 @@ const ControlBlock = struct { if_header: IfHeader, end_if: void, @"else": void, + elif_header: IfHeader, }; block: Data, strip_before: bool, @@ -553,6 +594,7 @@ const Keyword = enum { @"for", @"if", @"else", + @"elif", }; const EndKeyword = enum { @@ -646,4 +688,12 @@ test "template" { try testCase("{#if .val}1{/if}", .{ .val = true }, "1"); try testCase("{#for .vals |$v|}{$v}{/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); try testCase("{#for .vals |$v|=} {$v} {=/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); + try testCase("{#if .val}1{#else}0{/if}", .{ .val = true }, "1"); + try testCase("{#if .val}1{#else}0{/if}", .{ .val = false }, "0"); + try testCase("{#if .val}1{#elif .foo}2{/if}", .{ .val = false, .foo = true }, "2"); + try testCase("{#if .val}1{#elif .foo}2{/if}", .{ .val = false, .foo = false }, ""); + try testCase("{#if .val}1{#elif .foo}2{#else}0{/if}", .{ .val = false, .foo = false }, "0"); + try testCase("{#if .val}1{#elif .foo}2{#else}0{/if}", .{ .val = true, .foo = false }, "1"); + try testCase("{#if .val}1{#elif .foo}2{#else}0{/if}", .{ .val = false, .foo = true }, "2"); + try testCase("{#if .val}1{#elif .foo}2{#else}0{/if}", .{ .val = true, .foo = true }, "1"); } diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 6d62925..d8b0353 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,8 +14,13 @@ {$b}: {= /for =} {= /for} - {#if .qux}qux{#else}!qux{/if} - {#if .quxx}quxx{#else}!quxx{/if} + {#if .qux=} + qux + {=#elif .quxx=} + quxx + {=#else=} + neither + {=/if}