From 229acd6d3b03a34a0bb7127d3e93f8bad6b50889 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Fri, 18 Nov 2022 03:24:25 -0800 Subject: [PATCH] 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}