diff --git a/src/template/lib.zig b/src/template/lib.zig index aef9e1e..c997629 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -8,26 +8,20 @@ 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 * 8)); - - const tokens = comptime parseTemplateTokens(ControlTokenIter{ .text = template }); - const tmpl = comptime parseTemplate(tokens, 0, .root); + @setEvalBranchQuota(@intCast(u32, template.len * 6)); + const tmpl = comptime parseTemplate(TokenIter{ .text = template }, .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 { @@ -36,25 +30,19 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca const val = evaluateExpression(expr, args, captures); try print(writer, val); }, - .@"for" => |loop| { - const iterable = evaluateExpression(loop.header.iterable, args, captures); + .for_loop => |loop| { + const iterable = evaluateExpression(loop.iterable, args, captures); const subtemplate = loop.subtemplate; - //std.log.debug("{any}", .{subtemplate}); for (iterable) |v| { try executeTemplate( writer, subtemplate, args, - addCapture(captures, loop.header.capture, v), + addCapture(captures, loop.capture, v), ); } }, - .@"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"), } } @@ -124,250 +112,120 @@ fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture const TemplateType = enum { root, - for_block, - if_block, + subtemplate, }; -fn parseTemplate( - comptime tokens: []const TemplateToken, - comptime start: usize, - comptime template_type: TemplateType, -) ParseResult(usize, []const TemplateItem) { +fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult([]const TemplateItem) { comptime { - var i: usize = start; - var current_text: []const u8 = ""; + var iter = tokens; var items: []const TemplateItem = &.{}; + var current_text: []const u8 = ""; - 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| { - 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 = .{ - .@"if" = .{ - .subtemplate = subtemplate.item, - .header = header, - }, - }, - }}; - 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 = .{ - .@"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; + 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 = ""; } + 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"); + } + items = items ++ [_]TemplateItem{.{ .statement = result.item }}; } }, + .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 ++ "$", } - } 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 = i, + .new_iter = iter, .item = items, }; } } -const TemplateToken = union(enum) { - text: []const u8, - whitespace: []const u8, - control_block: ControlBlock, -}; - -fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken { +fn parseExpressionOrStatement( + comptime tokens: TokenIter, + comptime as_statement: bool, +) ParseResult(if (as_statement) Statement else Expression) { 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; - - 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 parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ControlBlock) { - comptime { - var iter = tokens; - - 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 '='"), + var stmt: Statement = 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); switch (keyword) { + .end_for => break .{ .end_for = {} }, .@"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 }; + const result = parseForLoop(iter); + // statemnt already finished so just return + return .{ + .new_iter = result.new_iter, + .item = .{ .for_loop = 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 => { + 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 => @compileError("TODO"), + else => if (as_statement) @compileError("TODO") else break, }; - // 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 = .{ - .block = stmt, - .strip_before = strip_before, - .strip_after = strip_after, + if (as_statement) { + // search for end of statement + while (iter.next()) |token| switch (token) { + .whitespace => {}, + .close_bracket => return .{ + .new_iter = iter, + .item = stmt, }, - }, - else => { - @compileLog(iter.row); - @compileError("TODO" ++ @tagName(token)); - }, - }; + else => { + @compileLog(iter.row); + @compileError("TODO" ++ @tagName(token)); + }, + }; - @compileError("Unexpected end of template"); + @compileError("Unexpected end of template"); + } else return .{ .new_iter = iter, .item = stmt.expression }; } } -fn skipWhitespace(comptime tokens: ControlTokenIter) ControlTokenIter { +fn skipWhitespace(comptime tokens: TokenIter) TokenIter { comptime { var iter = tokens; while (iter.peek()) |token| switch (token) { @@ -379,7 +237,7 @@ fn skipWhitespace(comptime tokens: ControlTokenIter) ControlTokenIter { } } -fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { +fn endStatement(comptime tokens: TokenIter) TokenIter { comptime { var iter = skipWhitespace(tokens); @@ -389,9 +247,9 @@ fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { } } -fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForHeader) { +fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { comptime { - const iterable = parseExpression(tokens); + const iterable = parseExpressionOrStatement(tokens, false); var iter = iterable.new_iter; iter = skipWhitespace(iter); @@ -412,32 +270,15 @@ fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIte const token = iter.next() orelse @compileError("Unexpected end of template"); if (token != .pipe) @compileError("Unexpected token"); } + iter = endStatement(iter); - return .{ - .new_iter = iter, - .item = .{ - .iterable = iterable.item, - .capture = capture, - }, - }; + const subtemplate = parseTemplate(iter, .subtemplate); + + return .{ .new_iter = subtemplate.new_iter, .item = .{ .iterable = iterable.item, .subtemplate = subtemplate.item, .capture = capture } }; } } -fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfHeader) { - comptime { - const condition = parseExpression(tokens); - var iter = condition.new_iter; - - return .{ - .new_iter = iter, - .item = .{ - .condition = condition.item, - }, - }; - } -} - -fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const []const u8) { +fn parseDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; @@ -464,9 +305,9 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [ } } -fn ParseResult(comptime It: type, comptime T: type) type { +fn ParseResult(comptime T: type) type { return struct { - new_iter: It, + new_iter: TokenIter, item: T, }; } @@ -481,55 +322,24 @@ const Expression = union(enum) { capture_deref: []const []const u8, }; -const For = struct { +const ForLoop = struct { subtemplate: []const TemplateItem, - header: ForHeader, -}; - -const ForHeader = struct { iterable: Expression, capture: []const u8, }; -const If = struct { - subtemplate: []const TemplateItem, - header: IfHeader, -}; - -const IfHeader = struct { - condition: Expression, -}; - const Statement = union(enum) { expression: Expression, - @"for": For, - @"if": If, -}; - -const ControlBlock = struct { - 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, + for_loop: ForLoop, + end_for: void, }; const Keyword = enum { @"for", - @"if", + end_for, }; -const EndKeyword = enum { - @"for", - @"if", -}; - -const ControlToken = union(enum) { +const Token = union(enum) { text: []const u8, open_bracket: void, close_bracket: void, @@ -538,18 +348,16 @@ const ControlToken = union(enum) { pound: void, pipe: void, dollar: void, - slash: void, - equals: void, }; -const ControlTokenIter = struct { +const TokenIter = struct { start: usize = 0, text: []const u8, - peeked_token: ?ControlToken = null, + peeked_token: ?Token = null, row: usize = 0, - fn next(self: *ControlTokenIter) ?ControlToken { + fn next(self: *TokenIter) ?Token { if (self.peeked_token) |token| { self.peeked_token = null; return token; @@ -567,8 +375,6 @@ const ControlTokenIter = struct { '#' => return .{ .pound = {} }, '|' => 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) {} @@ -580,7 +386,7 @@ const ControlTokenIter = 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] }; @@ -588,14 +394,9 @@ const ControlTokenIter = struct { } } - fn peek(self: *ControlTokenIter) ?ControlToken { + fn peek(self: *TokenIter) ?Token { const token = self.next(); self.peeked_token = token; return token; } - - fn putBack(self: *ControlTokenIter, token: ControlToken) void { - std.debug.assert(self.peeked_token == null); - self.peeked_token = token; - } }; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 72435f4..87bac71 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -1,7 +1,7 @@
-