From 5bb9742ab9ad19b6827abedb96be2f8f0052af89 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 17 Nov 2022 23:23:02 -0800 Subject: [PATCH 01/10] Change syntax for end_for statement --- src/template/lib.zig | 22 +++++++++++++++++++--- src/template/test.tmp.html | 4 ++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index c997629..12adb46 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -150,6 +150,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp .pound => current_text = current_text ++ "#", .pipe => current_text = current_text ++ "|", .dollar => current_text = current_text ++ "$", + .slash => current_text = current_text ++ "/", } } @@ -180,7 +181,6 @@ fn parseExpressionOrStatement( const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); switch (keyword) { - .end_for => break .{ .end_for = {} }, .@"for" => { const result = parseForLoop(iter); // statemnt already finished so just return @@ -193,6 +193,17 @@ fn parseExpressionOrStatement( //else => @compileError("TODO"), } }, + .slash => { + if (!as_statement) !@compileError("Unexpected Token"); + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next != .text) @compileError("Expected keyword following '/' character"); + const text = next.text; + const keyword = std.meta.stringToEnum(EndKeyword, text) orelse @compileError("Unknown keyword: " ++ text); + + switch (keyword) { + .@"for" => break .{ .end_for = {} }, + } + }, .period => { const names = parseDeref(iter); iter = names.new_iter; @@ -336,7 +347,10 @@ const Statement = union(enum) { const Keyword = enum { @"for", - end_for, +}; + +const EndKeyword = enum { + @"for", }; const Token = union(enum) { @@ -348,6 +362,7 @@ const Token = union(enum) { pound: void, pipe: void, dollar: void, + slash: void, }; const TokenIter = struct { @@ -375,6 +390,7 @@ const TokenIter = struct { '#' => return .{ .pound = {} }, '|' => return .{ .pipe = {} }, '$' => return .{ .dollar = {} }, + '/' => return .{ .slash = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} @@ -386,7 +402,7 @@ const TokenIter = 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 87bac71..c5f26db 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,8 +9,8 @@

{{ REAL BRACKETS }}

- {#for .baz |$f|}{#for $f |$b|}{$b}:{#end_for} - {#end_for} + {#for .baz |$f|}{#for $f |$b|}{$b}:{/for} + {/for}
From c9af99d08f6a6b7f8c0f8270191088356f428c3d Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 17 Nov 2022 23:39:32 -0800 Subject: [PATCH 02/10] Add if statements to templating engine --- src/template/lib.zig | 52 ++++++++++++++++++++++++++++++++++---- src/template/test.tmp.html | 2 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 12adb46..d86c3a6 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -8,11 +8,13 @@ pub fn main() !void { &.{ "5", "4", "3", "2", "1" }, &.{ "5", "4", "3", "2", "1" }, }, + .qux = true, + .quxx = false, }); } pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { - @setEvalBranchQuota(@intCast(u32, template.len * 6)); + @setEvalBranchQuota(@intCast(u32, template.len * 8)); const tmpl = comptime parseTemplate(TokenIter{ .text = template }, .root); try executeTemplate(writer, tmpl.item, args, .{}); } @@ -42,6 +44,11 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca ); } }, + .if_statement => |if_stmt| { + const condition = evaluateExpression(if_stmt.condition, args, captures); + const subtemplate = if_stmt.subtemplate; + if (condition) try executeTemplate(writer, subtemplate, args, captures); + }, else => @compileError("TODO"), } } @@ -112,7 +119,8 @@ fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture const TemplateType = enum { root, - subtemplate, + for_block, + if_block, }; fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult([]const TemplateItem) { @@ -137,7 +145,9 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp const result = parseExpressionOrStatement(iter, true); iter = result.new_iter; if (result.item == .end_for) { - if (template_type == .subtemplate) break :parse_loop else @compileError("Unexpected end statement"); + if (template_type == .for_block) break :parse_loop else @compileError("Unexpected end statement"); + } else if (result.item == .end_if) { + if (template_type == .if_block) break :parse_loop else @compileError("Unexpected end statement"); } items = items ++ [_]TemplateItem{.{ .statement = result.item }}; } @@ -189,6 +199,13 @@ fn parseExpressionOrStatement( .item = .{ .for_loop = result.item }, }; }, + .@"if" => { + const result = parseIfStatement(iter); + return .{ + .new_iter = result.new_iter, + .item = .{ .if_statement = result.item }, + }; + }, //else => @compileError("TODO"), } @@ -202,6 +219,7 @@ fn parseExpressionOrStatement( switch (keyword) { .@"for" => break .{ .end_for = {} }, + .@"if" => break .{ .end_if = {} }, } }, .period => { @@ -283,9 +301,24 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { } iter = endStatement(iter); - const subtemplate = parseTemplate(iter, .subtemplate); + const subtemplate = parseTemplate(iter, .for_block); - return .{ .new_iter = subtemplate.new_iter, .item = .{ .iterable = iterable.item, .subtemplate = subtemplate.item, .capture = capture } }; + return .{ .new_iter = subtemplate.new_iter, .item = .{ + .iterable = iterable.item, + .subtemplate = subtemplate.item, + .capture = capture, + } }; + } +} + +fn parseIfStatement(comptime tokens: TokenIter) ParseResult(IfStatement) { + comptime { + const condition = parseExpressionOrStatement(tokens, false); + var iter = endStatement(condition.new_iter); + + const subtemplate = parseTemplate(iter, .if_block); + + return .{ .new_iter = subtemplate.new_iter, .item = .{ .condition = condition.item, .subtemplate = subtemplate.item } }; } } @@ -339,18 +372,27 @@ const ForLoop = struct { capture: []const u8, }; +const IfStatement = struct { + subtemplate: []const TemplateItem, + condition: Expression, +}; + const Statement = union(enum) { expression: Expression, for_loop: ForLoop, end_for: void, + if_statement: IfStatement, + end_if: void, }; const Keyword = enum { @"for", + @"if", }; const EndKeyword = enum { @"for", + @"if", }; const Token = union(enum) { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index c5f26db..862b080 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -11,6 +11,8 @@
{#for .baz |$f|}{#for $f |$b|}{$b}:{/for} {/for} + {#if .qux}qux!{/if} + {#if .quxx}quxx!{/if}
From 22277beffc3f916e099a96cc71ed07f29afe22b2 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 17 Nov 2022 23:52:07 -0800 Subject: [PATCH 03/10] Add fields for whitespace stripping --- src/template/lib.zig | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index d86c3a6..7d1eebe 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -26,7 +26,7 @@ fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: }; } -fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { +fn executeStatement(writer: anytype, comptime stmt: ExecutableStatement, args: anytype, captures: anytype) !void { switch (stmt) { .expression => |expr| { const val = evaluateExpression(expr, args, captures); @@ -144,12 +144,13 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp } const result = parseExpressionOrStatement(iter, true); iter = result.new_iter; - if (result.item == .end_for) { + const stmt = result.item.executable; + if (stmt == .end_for) { if (template_type == .for_block) break :parse_loop else @compileError("Unexpected end statement"); - } else if (result.item == .end_if) { + } else if (stmt == .end_if) { if (template_type == .if_block) break :parse_loop else @compileError("Unexpected end statement"); } - items = items ++ [_]TemplateItem{.{ .statement = result.item }}; + items = items ++ [_]TemplateItem{.{ .statement = stmt }}; } }, .close_bracket => { @@ -181,7 +182,7 @@ fn parseExpressionOrStatement( ) ParseResult(if (as_statement) Statement else Expression) { comptime { var iter = tokens; - var stmt: Statement = while (iter.next()) |token| switch (token) { + var stmt: ExecutableStatement = while (iter.next()) |token| switch (token) { .whitespace => {}, .pound => { if (!as_statement) @compileError("Unexpected Token"); @@ -196,14 +197,22 @@ fn parseExpressionOrStatement( // statemnt already finished so just return return .{ .new_iter = result.new_iter, - .item = .{ .for_loop = result.item }, + .item = .{ + .executable = .{ .for_loop = result.item }, + .strip_before = false, + .strip_after = false, + }, }; }, .@"if" => { const result = parseIfStatement(iter); return .{ .new_iter = result.new_iter, - .item = .{ .if_statement = result.item }, + .item = .{ + .executable = .{ .if_statement = result.item }, + .strip_before = false, + .strip_after = false, + }, }; }, @@ -241,7 +250,11 @@ fn parseExpressionOrStatement( .whitespace => {}, .close_bracket => return .{ .new_iter = iter, - .item = stmt, + .item = .{ + .executable = stmt, + .strip_before = true, + .strip_after = true, + }, }, else => { @compileLog(iter.row); @@ -358,7 +371,7 @@ fn ParseResult(comptime T: type) type { const TemplateItem = union(enum) { text: []const u8, - statement: Statement, + statement: ExecutableStatement, }; const Expression = union(enum) { @@ -377,7 +390,8 @@ const IfStatement = struct { condition: Expression, }; -const Statement = union(enum) { +const ExecutableStatement = + union(enum) { expression: Expression, for_loop: ForLoop, end_for: void, @@ -385,6 +399,12 @@ const Statement = union(enum) { end_if: void, }; +const Statement = struct { + executable: ExecutableStatement, + strip_before: bool, + strip_after: bool, +}; + const Keyword = enum { @"for", @"if", From 58adc38945a8fa8fe8bab1d7613c610bb3499f49 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 00:09:10 -0800 Subject: [PATCH 04/10] Parse whitespace stripping chars --- src/template/lib.zig | 143 +++++++++++++++++++++---------------- src/template/test.tmp.html | 4 +- 2 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 7d1eebe..649b30e 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -162,6 +162,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp .pipe => current_text = current_text ++ "|", .dollar => current_text = current_text ++ "$", .slash => current_text = current_text ++ "/", + .equals => current_text = current_text ++ "=", } } @@ -182,78 +183,98 @@ fn parseExpressionOrStatement( ) ParseResult(if (as_statement) Statement else Expression) { comptime { var iter = tokens; - var stmt: ExecutableStatement = while (iter.next()) |token| switch (token) { - .whitespace => {}, - .pound => { - if (!as_statement) @compileError("Unexpected Token"); - const next = iter.next() orelse @compileError("Unexpected end of template"); - if (next != .text) @compileError("Expected keyword following '#' character"); - const text = next.text; - const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); + var first_token: bool = true; + var strip_before: bool = false; + var stmt: ExecutableStatement = while (iter.next()) |token| { + defer first_token = false; + switch (token) { + .equals => { + if (as_statement and first_token) { + strip_before = true; + } else @compileError("Unexpected '='"); + }, + .whitespace => {}, + .pound => { + if (!as_statement) @compileError("Unexpected Token"); + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next != .text) @compileError("Expected keyword following '#' character"); + const text = next.text; + const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); - switch (keyword) { - .@"for" => { - const result = parseForLoop(iter); - // statemnt already finished so just return - return .{ - .new_iter = result.new_iter, - .item = .{ - .executable = .{ .for_loop = result.item }, - .strip_before = false, - .strip_after = false, - }, - }; - }, - .@"if" => { - const result = parseIfStatement(iter); - return .{ - .new_iter = result.new_iter, - .item = .{ - .executable = .{ .if_statement = result.item }, - .strip_before = false, - .strip_after = false, - }, - }; - }, + switch (keyword) { + .@"for" => { + const result = parseForLoop(iter); + // statemnt already finished so just return + return .{ + .new_iter = result.new_iter, + .item = .{ + .executable = .{ .for_loop = result.item }, + .strip_before = false, + .strip_after = false, + }, + }; + }, + .@"if" => { + const result = parseIfStatement(iter); + return .{ + .new_iter = result.new_iter, + .item = .{ + .executable = .{ .if_statement = result.item }, + .strip_before = false, + .strip_after = false, + }, + }; + }, - //else => @compileError("TODO"), - } - }, - .slash => { - if (!as_statement) !@compileError("Unexpected Token"); - const next = iter.next() orelse @compileError("Unexpected end of template"); - if (next != .text) @compileError("Expected keyword following '/' character"); - const text = next.text; - const keyword = std.meta.stringToEnum(EndKeyword, text) orelse @compileError("Unknown keyword: " ++ text); + //else => @compileError("TODO"), + } + }, + .slash => { + if (!as_statement) !@compileError("Unexpected Token"); + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next != .text) @compileError("Expected keyword following '/' character"); + const text = next.text; + const keyword = std.meta.stringToEnum(EndKeyword, text) orelse @compileError("Unknown keyword: " ++ text); - switch (keyword) { - .@"for" => break .{ .end_for = {} }, - .@"if" => break .{ .end_if = {} }, - } - }, - .period => { - const names = parseDeref(iter); - iter = names.new_iter; - break .{ .expression = .{ .arg_deref = names.item } }; - }, - .dollar => { - const names = parseDeref(iter); - iter = names.new_iter; - break .{ .expression = .{ .capture_deref = names.item } }; - }, - else => if (as_statement) @compileError("TODO") else break, + switch (keyword) { + .@"for" => break .{ .end_for = {} }, + .@"if" => break .{ .end_if = {} }, + } + }, + .period => { + const names = parseDeref(iter); + iter = names.new_iter; + break .{ .expression = .{ .arg_deref = names.item } }; + }, + .dollar => { + const names = parseDeref(iter); + iter = names.new_iter; + break .{ .expression = .{ .capture_deref = names.item } }; + }, + else => if (as_statement) @compileError("TODO") else break, + } }; if (as_statement) { // search for end of statement + var strip_after: bool = false; while (iter.next()) |token| switch (token) { .whitespace => {}, + .equals => { + if (iter.peek()) |t| { + if (t == .close_bracket) { + strip_after = true; + continue; + } + } + @compileError("Unexpected '='"); + }, .close_bracket => return .{ .new_iter = iter, .item = .{ .executable = stmt, - .strip_before = true, - .strip_after = true, + .strip_before = strip_before, + .strip_after = strip_after, }, }, else => { @@ -425,6 +446,7 @@ const Token = union(enum) { pipe: void, dollar: void, slash: void, + equals: void, }; const TokenIter = struct { @@ -453,6 +475,7 @@ const TokenIter = struct { '|' => return .{ .pipe = {} }, '$' => return .{ .dollar = {} }, '/' => return .{ .slash = {} }, + '=' => return .{ .equals = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} @@ -464,7 +487,7 @@ const TokenIter = 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 862b080..bbfb3ad 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -1,7 +1,7 @@ - {.community.name} + {= .community.name =} @@ -9,7 +9,7 @@

{{ REAL BRACKETS }}

- {#for .baz |$f|}{#for $f |$b|}{$b}:{/for} + {= #for .baz |$f|}{#for $f |$b|}{$b}:{/for} {/for} {#if .qux}qux!{/if} {#if .quxx}quxx!{/if} From 1a7db9a83446263ae92e1f04d67e402f6a796989 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 01:58:13 -0800 Subject: [PATCH 05/10] Split expression/statements in parser --- src/template/lib.zig | 114 +++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 47 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 649b30e..fe88b9f 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -142,7 +142,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp items = items ++ [_]TemplateItem{.{ .text = current_text }}; current_text = ""; } - const result = parseExpressionOrStatement(iter, true); + const result = parseStatement(iter); iter = result.new_iter; const stmt = result.item.executable; if (stmt == .end_for) { @@ -177,10 +177,33 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp } } -fn parseExpressionOrStatement( - comptime tokens: TokenIter, - comptime as_statement: bool, -) ParseResult(if (as_statement) Statement else Expression) { +fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { + comptime { + var iter = tokens; + + var expr: Expression = while (iter.next()) |token| switch (token) { + .whitespace => {}, + .period => { + const names = parseDeref(iter); + iter = names.new_iter; + break .{ .arg_deref = names.item }; + }, + .dollar => { + const names = parseDeref(iter); + iter = names.new_iter; + break .{ .capture_deref = names.item }; + }, + else => @compileError("TODO"), + }; + + return .{ + .new_iter = iter, + .item = expr, + }; + } +} + +fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { comptime { var iter = tokens; var first_token: bool = true; @@ -189,13 +212,12 @@ fn parseExpressionOrStatement( defer first_token = false; switch (token) { .equals => { - if (as_statement and first_token) { + if (first_token) { strip_before = true; } else @compileError("Unexpected '='"); }, .whitespace => {}, .pound => { - if (!as_statement) @compileError("Unexpected Token"); const next = iter.next() orelse @compileError("Unexpected end of template"); if (next != .text) @compileError("Expected keyword following '#' character"); const text = next.text; @@ -230,7 +252,6 @@ fn parseExpressionOrStatement( } }, .slash => { - if (!as_statement) !@compileError("Unexpected Token"); const next = iter.next() orelse @compileError("Unexpected end of template"); if (next != .text) @compileError("Expected keyword following '/' character"); const text = next.text; @@ -241,50 +262,44 @@ fn parseExpressionOrStatement( .@"if" => break .{ .end_if = {} }, } }, - .period => { - const names = parseDeref(iter); - iter = names.new_iter; - break .{ .expression = .{ .arg_deref = names.item } }; + .period, .dollar => { + iter.putBack(token); + const expr = parseExpression(iter); + iter = expr.new_iter; + break .{ .expression = expr.item }; }, - .dollar => { - const names = parseDeref(iter); - iter = names.new_iter; - break .{ .expression = .{ .capture_deref = names.item } }; - }, - else => if (as_statement) @compileError("TODO") else break, + else => @compileError("TODO"), } }; - if (as_statement) { - // search for end of statement - var strip_after: bool = false; - while (iter.next()) |token| switch (token) { - .whitespace => {}, - .equals => { - if (iter.peek()) |t| { - if (t == .close_bracket) { - strip_after = true; - continue; - } + // search for end of statement + var strip_after: bool = false; + while (iter.next()) |token| switch (token) { + .whitespace => {}, + .equals => { + if (iter.peek()) |t| { + if (t == .close_bracket) { + strip_after = true; + continue; } - @compileError("Unexpected '='"); + } + @compileError("Unexpected '='"); + }, + .close_bracket => return .{ + .new_iter = iter, + .item = .{ + .executable = stmt, + .strip_before = strip_before, + .strip_after = strip_after, }, - .close_bracket => return .{ - .new_iter = iter, - .item = .{ - .executable = stmt, - .strip_before = strip_before, - .strip_after = strip_after, - }, - }, - else => { - @compileLog(iter.row); - @compileError("TODO" ++ @tagName(token)); - }, - }; + }, + else => { + @compileLog(iter.row); + @compileError("TODO" ++ @tagName(token)); + }, + }; - @compileError("Unexpected end of template"); - } else return .{ .new_iter = iter, .item = stmt.expression }; + @compileError("Unexpected end of template"); } } @@ -312,7 +327,7 @@ fn endStatement(comptime tokens: TokenIter) TokenIter { fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { comptime { - const iterable = parseExpressionOrStatement(tokens, false); + const iterable = parseExpression(tokens); var iter = iterable.new_iter; iter = skipWhitespace(iter); @@ -347,7 +362,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { fn parseIfStatement(comptime tokens: TokenIter) ParseResult(IfStatement) { comptime { - const condition = parseExpressionOrStatement(tokens, false); + const condition = parseExpression(tokens); var iter = endStatement(condition.new_iter); const subtemplate = parseTemplate(iter, .if_block); @@ -500,4 +515,9 @@ const TokenIter = struct { self.peeked_token = token; return token; } + + fn putBack(self: *TokenIter, token: Token) void { + std.debug.assert(self.peeked_token == null); + self.peeked_token = token; + } }; From 52bb8739f8ae74fbfd921bac87e456a661fc8851 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 01:59:27 -0800 Subject: [PATCH 06/10] Rename statement -> ControlBlock --- src/template/lib.zig | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index fe88b9f..56b9946 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -26,7 +26,7 @@ fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: }; } -fn executeStatement(writer: anytype, comptime stmt: ExecutableStatement, args: anytype, captures: anytype) !void { +fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { switch (stmt) { .expression => |expr| { const val = evaluateExpression(expr, args, captures); @@ -142,9 +142,9 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp items = items ++ [_]TemplateItem{.{ .text = current_text }}; current_text = ""; } - const result = parseStatement(iter); + const result = parseControlBlock(iter); iter = result.new_iter; - const stmt = result.item.executable; + const stmt = result.item.statement; if (stmt == .end_for) { if (template_type == .for_block) break :parse_loop else @compileError("Unexpected end statement"); } else if (stmt == .end_if) { @@ -203,12 +203,12 @@ fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { } } -fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { +fn parseControlBlock(comptime tokens: TokenIter) ParseResult(ControlBlock) { comptime { var iter = tokens; var first_token: bool = true; var strip_before: bool = false; - var stmt: ExecutableStatement = while (iter.next()) |token| { + var stmt: Statement = while (iter.next()) |token| { defer first_token = false; switch (token) { .equals => { @@ -230,7 +230,7 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { return .{ .new_iter = result.new_iter, .item = .{ - .executable = .{ .for_loop = result.item }, + .statement = .{ .for_loop = result.item }, .strip_before = false, .strip_after = false, }, @@ -241,7 +241,7 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { return .{ .new_iter = result.new_iter, .item = .{ - .executable = .{ .if_statement = result.item }, + .statement = .{ .if_statement = result.item }, .strip_before = false, .strip_after = false, }, @@ -288,7 +288,7 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { .close_bracket => return .{ .new_iter = iter, .item = .{ - .executable = stmt, + .statement = stmt, .strip_before = strip_before, .strip_after = strip_after, }, @@ -315,7 +315,7 @@ fn skipWhitespace(comptime tokens: TokenIter) TokenIter { } } -fn endStatement(comptime tokens: TokenIter) TokenIter { +fn endControlBlock(comptime tokens: TokenIter) TokenIter { comptime { var iter = skipWhitespace(tokens); @@ -348,7 +348,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { const token = iter.next() orelse @compileError("Unexpected end of template"); if (token != .pipe) @compileError("Unexpected token"); } - iter = endStatement(iter); + iter = endControlBlock(iter); const subtemplate = parseTemplate(iter, .for_block); @@ -363,7 +363,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { fn parseIfStatement(comptime tokens: TokenIter) ParseResult(IfStatement) { comptime { const condition = parseExpression(tokens); - var iter = endStatement(condition.new_iter); + var iter = endControlBlock(condition.new_iter); const subtemplate = parseTemplate(iter, .if_block); @@ -407,7 +407,7 @@ fn ParseResult(comptime T: type) type { const TemplateItem = union(enum) { text: []const u8, - statement: ExecutableStatement, + statement: Statement, }; const Expression = union(enum) { @@ -426,8 +426,7 @@ const IfStatement = struct { condition: Expression, }; -const ExecutableStatement = - union(enum) { +const Statement = union(enum) { expression: Expression, for_loop: ForLoop, end_for: void, @@ -435,8 +434,8 @@ const ExecutableStatement = end_if: void, }; -const Statement = struct { - executable: ExecutableStatement, +const ControlBlock = struct { + statement: Statement, strip_before: bool, strip_after: bool, }; From 1340862c2875c683e9cc277052c2b2a207e03070 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 02:02:24 -0800 Subject: [PATCH 07/10] Add It param to ParseResult --- src/template/lib.zig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 56b9946..7c402f9 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -123,7 +123,7 @@ const TemplateType = enum { if_block, }; -fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult([]const TemplateItem) { +fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult(TokenIter, []const TemplateItem) { comptime { var iter = tokens; var items: []const TemplateItem = &.{}; @@ -177,7 +177,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp } } -fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { +fn parseExpression(comptime tokens: TokenIter) ParseResult(TokenIter, Expression) { comptime { var iter = tokens; @@ -203,7 +203,7 @@ fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { } } -fn parseControlBlock(comptime tokens: TokenIter) ParseResult(ControlBlock) { +fn parseControlBlock(comptime tokens: TokenIter) ParseResult(TokenIter, ControlBlock) { comptime { var iter = tokens; var first_token: bool = true; @@ -325,7 +325,7 @@ fn endControlBlock(comptime tokens: TokenIter) TokenIter { } } -fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { +fn parseForLoop(comptime tokens: TokenIter) ParseResult(TokenIter, ForLoop) { comptime { const iterable = parseExpression(tokens); var iter = iterable.new_iter; @@ -360,7 +360,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { } } -fn parseIfStatement(comptime tokens: TokenIter) ParseResult(IfStatement) { +fn parseIfStatement(comptime tokens: TokenIter) ParseResult(TokenIter, IfStatement) { comptime { const condition = parseExpression(tokens); var iter = endControlBlock(condition.new_iter); @@ -371,7 +371,7 @@ fn parseIfStatement(comptime tokens: TokenIter) ParseResult(IfStatement) { } } -fn parseDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { +fn parseDeref(comptime tokens: TokenIter) ParseResult(TokenIter, []const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; @@ -398,9 +398,9 @@ fn parseDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { } } -fn ParseResult(comptime T: type) type { +fn ParseResult(comptime It: type, comptime T: type) type { return struct { - new_iter: TokenIter, + new_iter: It, item: T, }; } From 802e6402bf170b18979e86bf7b7f8bf7c1b0112f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 02:05:13 -0800 Subject: [PATCH 08/10] Token -> ControlToken --- src/template/lib.zig | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 7c402f9..b884144 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -15,7 +15,7 @@ pub fn main() !void { pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { @setEvalBranchQuota(@intCast(u32, template.len * 8)); - const tmpl = comptime parseTemplate(TokenIter{ .text = template }, .root); + const tmpl = comptime parseTemplate(ControlTokenIter{ .text = template }, .root); try executeTemplate(writer, tmpl.item, args, .{}); } @@ -123,7 +123,7 @@ const TemplateType = enum { if_block, }; -fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult(TokenIter, []const TemplateItem) { +fn parseTemplate(comptime tokens: ControlTokenIter, comptime template_type: TemplateType) ParseResult(ControlTokenIter, []const TemplateItem) { comptime { var iter = tokens; var items: []const TemplateItem = &.{}; @@ -177,7 +177,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp } } -fn parseExpression(comptime tokens: TokenIter) ParseResult(TokenIter, Expression) { +fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, Expression) { comptime { var iter = tokens; @@ -203,7 +203,7 @@ fn parseExpression(comptime tokens: TokenIter) ParseResult(TokenIter, Expression } } -fn parseControlBlock(comptime tokens: TokenIter) ParseResult(TokenIter, ControlBlock) { +fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ControlBlock) { comptime { var iter = tokens; var first_token: bool = true; @@ -303,7 +303,7 @@ fn parseControlBlock(comptime tokens: TokenIter) ParseResult(TokenIter, ControlB } } -fn skipWhitespace(comptime tokens: TokenIter) TokenIter { +fn skipWhitespace(comptime tokens: ControlTokenIter) ControlTokenIter { comptime { var iter = tokens; while (iter.peek()) |token| switch (token) { @@ -315,7 +315,7 @@ fn skipWhitespace(comptime tokens: TokenIter) TokenIter { } } -fn endControlBlock(comptime tokens: TokenIter) TokenIter { +fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { comptime { var iter = skipWhitespace(tokens); @@ -325,7 +325,7 @@ fn endControlBlock(comptime tokens: TokenIter) TokenIter { } } -fn parseForLoop(comptime tokens: TokenIter) ParseResult(TokenIter, ForLoop) { +fn parseForLoop(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForLoop) { comptime { const iterable = parseExpression(tokens); var iter = iterable.new_iter; @@ -360,7 +360,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(TokenIter, ForLoop) { } } -fn parseIfStatement(comptime tokens: TokenIter) ParseResult(TokenIter, IfStatement) { +fn parseIfStatement(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfStatement) { comptime { const condition = parseExpression(tokens); var iter = endControlBlock(condition.new_iter); @@ -371,7 +371,7 @@ fn parseIfStatement(comptime tokens: TokenIter) ParseResult(TokenIter, IfStateme } } -fn parseDeref(comptime tokens: TokenIter) ParseResult(TokenIter, []const []const u8) { +fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; @@ -450,7 +450,7 @@ const EndKeyword = enum { @"if", }; -const Token = union(enum) { +const ControlToken = union(enum) { text: []const u8, open_bracket: void, close_bracket: void, @@ -463,14 +463,14 @@ const Token = union(enum) { equals: void, }; -const TokenIter = struct { +const ControlTokenIter = struct { start: usize = 0, text: []const u8, - peeked_token: ?Token = null, + peeked_token: ?ControlToken = null, row: usize = 0, - fn next(self: *TokenIter) ?Token { + fn next(self: *ControlTokenIter) ?ControlToken { if (self.peeked_token) |token| { self.peeked_token = null; return token; @@ -509,13 +509,13 @@ const TokenIter = struct { } } - fn peek(self: *TokenIter) ?Token { + fn peek(self: *ControlTokenIter) ?ControlToken { const token = self.next(); self.peeked_token = token; return token; } - fn putBack(self: *TokenIter, token: Token) void { + fn putBack(self: *ControlTokenIter, token: ControlToken) void { std.debug.assert(self.peeked_token == null); self.peeked_token = token; } From 229acd6d3b03a34a0bb7127d3e93f8bad6b50889 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 03:24:25 -0800 Subject: [PATCH 09/10] Refactor template code --- src/template/lib.zig | 344 ++++++++++++++++++++++--------------- src/template/test.tmp.html | 7 +- 2 files changed, 215 insertions(+), 136 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index b884144..a32b5df 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -15,15 +15,19 @@ pub fn main() !void { pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { @setEvalBranchQuota(@intCast(u32, template.len * 8)); - const tmpl = comptime parseTemplate(ControlTokenIter{ .text = template }, .root); + + const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template }); + const tmpl = comptime parseTemplate(tokens, 0, .root); try executeTemplate(writer, tmpl.item, args, .{}); } fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: anytype, captures: anytype) !void { - inline for (items) |it| switch (it) { - .text => |text| try writer.writeAll(text), - .statement => |stmt| try executeStatement(writer, stmt, args, captures), - }; + inline for (items) |it| { + switch (it) { + .text => |text| try writer.writeAll(text), + .statement => |stmt| try executeStatement(writer, stmt, args, captures), + } + } } fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { @@ -32,24 +36,24 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca const val = evaluateExpression(expr, args, captures); try print(writer, val); }, - .for_loop => |loop| { - const iterable = evaluateExpression(loop.iterable, args, captures); + .@"for" => |loop| { + const iterable = evaluateExpression(loop.header.iterable, args, captures); const subtemplate = loop.subtemplate; for (iterable) |v| { try executeTemplate( writer, subtemplate, args, - addCapture(captures, loop.capture, v), + addCapture(captures, loop.header.capture, v), ); } }, - .if_statement => |if_stmt| { - const condition = evaluateExpression(if_stmt.condition, args, captures); + .@"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); }, - else => @compileError("TODO"), + //else => @compileError("TODO"), } } @@ -123,60 +127,129 @@ const TemplateType = enum { if_block, }; -fn parseTemplate(comptime tokens: ControlTokenIter, comptime template_type: TemplateType) ParseResult(ControlTokenIter, []const TemplateItem) { +fn parseTemplate( + comptime tokens: []const TemplateToken, + comptime start: usize, + comptime template_type: TemplateType, +) ParseResult(usize, []const TemplateItem) { comptime { - var iter = tokens; - var items: []const TemplateItem = &.{}; + var i: usize = start; var current_text: []const u8 = ""; + var items: []const TemplateItem = &.{}; - parse_loop: while (iter.next()) |token| { - switch (token) { - .whitespace, .text => |text| current_text = current_text ++ text, - .open_bracket => { - const next = iter.peek() orelse @compileError("Unexpected end of template"); - if (next == .open_bracket) { - current_text = current_text ++ "{"; - _ = iter.next(); - } else { - if (current_text.len != 0) { - items = items ++ [_]TemplateItem{.{ .text = current_text }}; - current_text = ""; + while (i < tokens.len) : (i += 1) { + 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; + current_text = current_text ++ wsp; + }, + .control_block => |cb| { + if (current_text.len != 0) { + items = items ++ [_]TemplateItem{.{ .text = current_text }}; + current_text = ""; + } + + switch (cb.block) { + .expression => |expr| items = items ++ [_]TemplateItem{.{ .statement = .{ .expression = expr } }}, + .if_header => |header| { + const subtemplate = parseTemplate(tokens, i + 1, .if_block); + items = items ++ [_]TemplateItem{.{ + .statement = .{ + .@"if" = .{ + .subtemplate = subtemplate.item, + .header = header, + }, + }, + }}; + i = subtemplate.new_iter; + }, + .for_header => |header| { + const subtemplate = parseTemplate(tokens, i + 1, .for_block); + items = items ++ [_]TemplateItem{.{ + .statement = .{ + .@"for" = .{ + .subtemplate = subtemplate.item, + .header = header, + }, + }, + }}; + i = subtemplate.new_iter; + }, + .end_for => if (template_type == .for_block) + break + else + @compileError("Unexpected /for tag"), + .end_if => if (template_type == .if_block) + break + else + @compileError("Unexpected /if 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; } - const result = parseControlBlock(iter); - iter = result.new_iter; - const stmt = result.item.statement; - if (stmt == .end_for) { - if (template_type == .for_block) break :parse_loop else @compileError("Unexpected end statement"); - } else if (stmt == .end_if) { - if (template_type == .if_block) break :parse_loop else @compileError("Unexpected end statement"); - } - items = items ++ [_]TemplateItem{.{ .statement = stmt }}; } }, - .close_bracket => { - const next = iter.next() orelse @compileError("Unexpected end of template"); - if (next == .close_bracket) current_text = current_text ++ "}" else @compileError("Unpaired close bracket, did you mean \"}}\"?"); - }, - .period => current_text = current_text ++ ".", - .pound => current_text = current_text ++ "#", - .pipe => current_text = current_text ++ "|", - .dollar => current_text = current_text ++ "$", - .slash => current_text = current_text ++ "/", - .equals => current_text = current_text ++ "=", } - } + } else if (template_type != .root) @compileError("End tag not found"); - if (current_text.len != 0) { - items = items ++ [_]TemplateItem{.{ .text = current_text }}; - } + if (current_text.len != 0) items = items ++ [_]TemplateItem{.{ .text = current_text }}; return .{ - .new_iter = iter, + .new_iter = i, .item = items, }; } } +const TemplateToken = union(enum) { + text: []const u8, + whitespace: []const u8, + control_block: ControlBlock, +}; + +fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken { + comptime { + var iter = tokens; + var items: []const TemplateToken = &.{}; + + while (iter.next()) |token| switch (token) { + .whitespace => |wsp| items = items ++ [_]TemplateToken{.{ .whitespace = wsp }}, + .text => |text| items = items ++ [_]TemplateToken{.{ .text = text }}, + .open_bracket => { + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next == .open_bracket) { + items = items ++ [_]TemplateToken{.{ .text = "{" }}; + } else { + iter.putBack(next); + const result = parseControlBlock(iter); + iter = result.new_iter; + items = items ++ [_]TemplateToken{.{ .control_block = result.item }}; + } + }, + .close_bracket => { + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next == .close_bracket) + items = items ++ [_]TemplateToken{.{ .text = "}" }} + else + @compileError("Unpaired close bracket, did you mean \"}}\"?"); + }, + .period => items = items ++ [_]TemplateToken{.{ .text = "." }}, + .pound => items = items ++ [_]TemplateToken{.{ .text = "#" }}, + .pipe => items = items ++ [_]TemplateToken{.{ .text = "|" }}, + .dollar => items = items ++ [_]TemplateToken{.{ .text = "$" }}, + .slash => items = items ++ [_]TemplateToken{.{ .text = "/" }}, + .equals => items = items ++ [_]TemplateToken{.{ .text = "=" }}, + }; + + return items; + } +} + fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, Expression) { comptime { var iter = tokens; @@ -206,70 +279,58 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ControlBlock) { comptime { var iter = tokens; - var first_token: bool = true; - var strip_before: bool = false; - var stmt: Statement = while (iter.next()) |token| { - defer first_token = false; - switch (token) { - .equals => { - if (first_token) { - strip_before = true; - } else @compileError("Unexpected '='"); - }, - .whitespace => {}, - .pound => { - const next = iter.next() orelse @compileError("Unexpected end of template"); - if (next != .text) @compileError("Expected keyword following '#' character"); - const text = next.text; - const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); - switch (keyword) { - .@"for" => { - const result = parseForLoop(iter); - // statemnt already finished so just return - return .{ - .new_iter = result.new_iter, - .item = .{ - .statement = .{ .for_loop = result.item }, - .strip_before = false, - .strip_after = false, - }, - }; - }, - .@"if" => { - const result = parseIfStatement(iter); - return .{ - .new_iter = result.new_iter, - .item = .{ - .statement = .{ .if_statement = result.item }, - .strip_before = false, - .strip_after = false, - }, - }; - }, - - //else => @compileError("TODO"), - } - }, - .slash => { - const next = iter.next() orelse @compileError("Unexpected end of template"); - if (next != .text) @compileError("Expected keyword following '/' character"); - const text = next.text; - const keyword = std.meta.stringToEnum(EndKeyword, text) orelse @compileError("Unknown keyword: " ++ text); - - switch (keyword) { - .@"for" => break .{ .end_for = {} }, - .@"if" => break .{ .end_if = {} }, - } - }, - .period, .dollar => { - iter.putBack(token); - const expr = parseExpression(iter); - iter = expr.new_iter; - break .{ .expression = expr.item }; - }, - else => @compileError("TODO"), + const strip_before = if (iter.next()) |first| blk: { + if (first == .equals) { + break :blk true; } + + iter.putBack(first); + break :blk false; + } else @compileError("Unexpected end of template"); + + var stmt: ControlBlock.Data = while (iter.next()) |token| switch (token) { + .equals => @compileError("Unexpected '='"), + .whitespace => {}, + .pound => { + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next != .text) @compileError("Expected keyword following '#' character"); + const text = next.text; + const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); + + switch (keyword) { + .@"for" => { + const result = parseForHeader(iter); + iter = result.new_iter; + break .{ .for_header = result.item }; + }, + .@"if" => { + const result = parseIfHeader(iter); + iter = result.new_iter; + break .{ .if_header = result.item }; + }, + + //else => @compileError("TODO"), + } + }, + .slash => { + const next = iter.next() orelse @compileError("Unexpected end of template"); + if (next != .text) @compileError("Expected keyword following '/' character"); + const text = next.text; + const keyword = std.meta.stringToEnum(EndKeyword, text) orelse @compileError("Unknown keyword: " ++ text); + + switch (keyword) { + .@"for" => break .{ .end_for = {} }, + .@"if" => break .{ .end_if = {} }, + } + }, + .period, .dollar => { + iter.putBack(token); + const expr = parseExpression(iter); + iter = expr.new_iter; + break .{ .expression = expr.item }; + }, + else => @compileError("TODO"), }; // search for end of statement @@ -288,7 +349,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken .close_bracket => return .{ .new_iter = iter, .item = .{ - .statement = stmt, + .block = stmt, .strip_before = strip_before, .strip_after = strip_after, }, @@ -325,7 +386,7 @@ fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { } } -fn parseForLoop(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForLoop) { +fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForHeader) { comptime { const iterable = parseExpression(tokens); var iter = iterable.new_iter; @@ -348,26 +409,28 @@ fn parseForLoop(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, const token = iter.next() orelse @compileError("Unexpected end of template"); if (token != .pipe) @compileError("Unexpected token"); } - iter = endControlBlock(iter); - const subtemplate = parseTemplate(iter, .for_block); - - return .{ .new_iter = subtemplate.new_iter, .item = .{ - .iterable = iterable.item, - .subtemplate = subtemplate.item, - .capture = capture, - } }; + return .{ + .new_iter = iter, + .item = .{ + .iterable = iterable.item, + .capture = capture, + }, + }; } } -fn parseIfStatement(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfStatement) { +fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfHeader) { comptime { const condition = parseExpression(tokens); - var iter = endControlBlock(condition.new_iter); + var iter = condition.new_iter; - const subtemplate = parseTemplate(iter, .if_block); - - return .{ .new_iter = subtemplate.new_iter, .item = .{ .condition = condition.item, .subtemplate = subtemplate.item } }; + return .{ + .new_iter = iter, + .item = .{ + .condition = condition.item, + }, + }; } } @@ -415,27 +478,40 @@ const Expression = union(enum) { capture_deref: []const []const u8, }; -const ForLoop = struct { +const For = struct { subtemplate: []const TemplateItem, + header: ForHeader, +}; + +const ForHeader = struct { iterable: Expression, capture: []const u8, }; -const IfStatement = struct { +const If = struct { subtemplate: []const TemplateItem, + header: IfHeader, +}; + +const IfHeader = struct { condition: Expression, }; const Statement = union(enum) { expression: Expression, - for_loop: ForLoop, - end_for: void, - if_statement: IfStatement, - end_if: void, + @"for": For, + @"if": If, }; const ControlBlock = struct { - statement: Statement, + const Data = union(enum) { + expression: Expression, + for_header: ForHeader, + end_for: void, + if_header: IfHeader, + end_if: void, + }; + block: Data, strip_before: bool, strip_after: bool, }; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index bbfb3ad..413f49d 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,8 +9,11 @@

{{ REAL BRACKETS }}

- {= #for .baz |$f|}{#for $f |$b|}{$b}:{/for} - {/for} + {= #for .baz |$f| =} + {= #for $f |$b| =} + {$b}: + {= /for =} + {= /for =} {#if .qux}qux!{/if} {#if .quxx}quxx!{/if}
From fdf0a8459314c46110d53da8b3968c4735d1f614 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 03:32:30 -0800 Subject: [PATCH 10/10] Fix whitespace stripping after control blocks --- src/template/lib.zig | 3 +++ src/template/test.tmp.html | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index a32b5df..aef9e1e 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -39,6 +39,7 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca .@"for" => |loop| { const iterable = evaluateExpression(loop.header.iterable, args, captures); const subtemplate = loop.subtemplate; + //std.log.debug("{any}", .{subtemplate}); for (iterable) |v| { try executeTemplate( writer, @@ -155,6 +156,7 @@ 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); items = items ++ [_]TemplateItem{.{ .statement = .{ @@ -167,6 +169,7 @@ fn parseTemplate( i = subtemplate.new_iter; }, .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 = .{ diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 413f49d..72435f4 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,12 +9,12 @@

{{ REAL BRACKETS }}

- {= #for .baz |$f| =} + {#for .baz |$f| =} {= #for $f |$b| =} {$b}: {= /for =} - {= /for =} - {#if .qux}qux!{/if} + {= /for} + {#if .qux}qux!{/if=} {#if .quxx}quxx!{/if}