const std = @import("std"); pub fn main() !void { try execute(std.io.getStdOut().writer(), @embedFile("./test.tmp.html"), .{ .community = .{ .name = "abcd" } }); } const logging = false; pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { const items = comptime parseTemplate(template); try executeTemplate(writer, items, args); } fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: anytype) !void { inline for (items) |it| switch (it) { .text => |text| try writer.writeAll(text), .statement => |stmt| try executeStatement(writer, stmt, args), }; } fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype) !void { switch (stmt) { .expression => |expr| switch (expr) { .arg_deref => |fields| try argDeref(writer, fields, args), }, else => @compileError("TODO"), } } fn argDeref(writer: anytype, comptime names: []const []const u8, arg: anytype) !void { if (names.len == 0) { const T = @TypeOf(arg); if (comptime std.meta.trait.isZigString(T)) return writer.writeAll(arg); return std.fmt.format(writer, "{any}", .{arg}); } return argDeref(writer, names[1..], @field(arg, names[0])); } fn parseTemplate(comptime template: []const u8) []const TemplateItem { comptime { @setEvalBranchQuota(@intCast(u32, template.len * 6)); var iter = TokenIter{ .text = template }; var items: []const TemplateItem = &.{}; var current_text: []const u8 = ""; while (iter.next()) |token| { if (logging) @compileLog(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 = parseStatement(iter); iter = result.new_iter; 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 ++ "#", } } if (current_text.len != 0) { items = items ++ [_]TemplateItem{.{ .text = current_text }}; } return items; } } fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { comptime { var iter = tokens; while (iter.next()) |token| switch (token) { .whitespace => {}, .pound => { const next = iter.next() orelse @compileError("Unexpected end of template"); if (logging) @compileLog("keyword", next); if (next != .text) @compileError("Expected keyword following '#' character"); const text = next.text; const keyword = std.meta.stringToEnum(Keyword, text) orelse @compileError("Unknown keyword: " ++ text); _ = keyword; @panic("todo"); }, .period => { const expr = parseArgDeref(iter); iter = expr.new_iter; while (iter.next()) |it| switch (it) { .whitespace => {}, .close_bracket => break, else => @compileError("TODO"), }; return .{ .new_iter = iter, .item = .{ .expression = .{ .arg_deref = expr.item }, } }; }, else => @compileError(""), }; @compileError("Unexpected end of template"); } } fn parseArgDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; var wants = .text; while (iter.peek()) |token| { switch (token) { .whitespace => {}, .text => |text| { if (wants != .text) @compileError("Unexpected token \"" ++ text ++ "\""); fields = fields ++ [1][]const u8{text}; wants = .period; }, .period => { if (wants != .period) @compileError("Unexpected token \".\""); wants = .text; }, else => if (wants == .period) return .{ .new_iter = iter, .item = fields, } else @compileError("Unexpected token"), } _ = iter.next(); } } } fn ParseResult(comptime T: type) type { return struct { new_iter: TokenIter, item: T, }; } const TemplateItem = union(enum) { text: []const u8, statement: Statement, }; const Expression = union(enum) { arg_deref: []const []const u8, }; const Statement = union(enum) { expression: Expression, for_loop: struct { subtemplate: []const u8, indexable: Expression, iteration_capture: []const u8, index_capture: ?[]const u8, }, }; const State = enum { text, text_close_bracket, template_start, template, }; const Keyword = enum { @"for", }; const Token = union(enum) { text: []const u8, open_bracket: void, close_bracket: void, period: void, whitespace: []const u8, pound: void, }; const TokenIter = struct { start: usize = 0, text: []const u8, peeked_token: ?Token = null, fn next(self: *TokenIter) ?Token { if (self.peeked_token) |token| { self.peeked_token = null; return token; } const remaining = self.text[self.start..]; if (remaining.len == 0) return null; const ch = remaining[0]; self.start += 1; switch (ch) { '{' => return .{ .open_bracket = {} }, '}' => return .{ .close_bracket = {} }, '.' => return .{ .period = {} }, '#' => return .{ .pound = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} self.start += idx - 1; return .{ .whitespace = remaining[0..idx] }; }, else => { var idx: usize = 0; 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] }; }, } } fn peek(self: *TokenIter) ?Token { const token = self.next(); self.peeked_token = token; return token; } };