From 3b03764be732f5b393d5d735d0ad34bedaeb1487 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 19:10:16 -0800 Subject: [PATCH 01/12] Start work on template package --- src/template/lib.zig | 121 +++++++++++++++++++++++++++++++++++++ src/template/test.tmp.html | 18 ++++++ 2 files changed, 139 insertions(+) create mode 100644 src/template/lib.zig create mode 100644 src/template/test.tmp.html diff --git a/src/template/lib.zig b/src/template/lib.zig new file mode 100644 index 0000000..fb20ec4 --- /dev/null +++ b/src/template/lib.zig @@ -0,0 +1,121 @@ +const std = @import("std"); + +pub fn main() !void { + try execute(std.io.getStdOut().writer(), @embedFile("./test.tmp.html"), .{ .community = .{ .name = "abcd" } }); +} + +pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { + @setEvalBranchQuota(@intCast(u32, template.len * 6)); + comptime var iter = TokenIter{ .text = template }; + comptime var state: State = .text; + inline while (comptime iter.next()) |token| { + switch (token) { + .text => |text| switch (state) { + .text => try writer.writeAll(text), + else => @compileError("Text token not allowed in state " ++ @tagName(state) ++ text), + }, + .open_bracket => switch (state) { + .text => state = .template_start, + .template_start => { + try writer.writeByte('{'); + state = .text; + }, + else => @compileError(""), + }, + .close_bracket => switch (state) { + .text => state = .text_close_bracket, + .text_close_bracket => { + try writer.writeByte('}'); + state = .text; + }, + .template_start, .template => state = .text, + //else => @compileError(""), + }, + .whitespace => |wsp| switch (state) { + .text => try writer.writeAll(wsp), + else => {}, + }, + .period => switch (state) { + .text => try writer.writeByte('.'), + .template_start, .template => { + try argDeref(writer, &iter, args); + state = .template; + }, + else => @compileError(""), + }, + } + } +} + +fn argDeref(writer: anytype, comptime iter: *TokenIter, arg: anytype) !void { + inline while (comptime iter.peek()) |token| { + switch (token) { + .period => {}, + .text => |text| { + _ = comptime iter.next(); + return argDeref(writer, iter, @field(arg, text)); + }, + else => return try writer.writeAll(arg), + } + _ = comptime iter.next(); + } +} + +const State = enum { + text, + text_close_bracket, + template_start, + template, +}; + +const Token = union(enum) { + text: []const u8, + open_bracket: void, + close_bracket: void, + period: void, + whitespace: []const u8, +}; + +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 = {} }, + ' ', '\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; + } +}; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html new file mode 100644 index 0000000..9c2ce7b --- /dev/null +++ b/src/template/test.tmp.html @@ -0,0 +1,18 @@ + + + + { .community.name } + + + +

TITLE

+

{{ REAL BRACKETS }}

+ +
+ {{#for args.notes |$note, $i|}} +

Note no. {{$i}}

+ {{#template note_display ($note)}} + {{/for}} +
+ + From 7191ad8f27b47fc224222771097dcff30ab03c65 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 22:07:45 -0800 Subject: [PATCH 02/12] Split up template parsing and execution --- src/template/lib.zig | 170 +++++++++++++++++++++++++++---------- src/template/test.tmp.html | 5 +- 2 files changed, 130 insertions(+), 45 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index fb20ec4..7cf23d6 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -4,63 +4,141 @@ 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 { @setEvalBranchQuota(@intCast(u32, template.len * 6)); comptime var iter = TokenIter{ .text = template }; - comptime var state: State = .text; inline while (comptime iter.next()) |token| { + if (logging) @compileLog(token); switch (token) { - .text => |text| switch (state) { - .text => try writer.writeAll(text), - else => @compileError("Text token not allowed in state " ++ @tagName(state) ++ text), - }, - .open_bracket => switch (state) { - .text => state = .template_start, - .template_start => { + .text => |text| try writer.writeAll(text), + .open_bracket => { + const next = comptime iter.peek() orelse @compileError("Unexpected end of template"); + if (next == .open_bracket) { try writer.writeByte('{'); - state = .text; - }, - else => @compileError(""), + _ = comptime iter.next(); + } else { + const result = comptime parseStatement(iter); + try executeStatement(writer, result.statement, args); + iter = result.new_iter; + } }, - .close_bracket => switch (state) { - .text => state = .text_close_bracket, - .text_close_bracket => { - try writer.writeByte('}'); - state = .text; - }, - .template_start, .template => state = .text, - //else => @compileError(""), - }, - .whitespace => |wsp| switch (state) { - .text => try writer.writeAll(wsp), - else => {}, - }, - .period => switch (state) { - .text => try writer.writeByte('.'), - .template_start, .template => { - try argDeref(writer, &iter, args); - state = .template; - }, - else => @compileError(""), + .close_bracket => { + const next = comptime iter.next() orelse @compileError("Unexpected end of template"); + if (comptime next == .close_bracket) try writer.writeByte('}') else @compileError("Unpaired close bracket, did you mean \"}}\"?"); }, + .whitespace => |wsp| try writer.writeAll(wsp), + .period => try writer.writeByte('.'), + .pound => try writer.writeByte('#'), } } } -fn argDeref(writer: anytype, comptime iter: *TokenIter, arg: anytype) !void { - inline while (comptime iter.peek()) |token| { - switch (token) { - .period => {}, - .text => |text| { - _ = comptime iter.next(); - return argDeref(writer, iter, @field(arg, text)); - }, - else => return try writer.writeAll(arg), - } - _ = comptime iter.next(); +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 parseStatement(comptime tokens: TokenIter) StatementResult { + 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, .statement = .{ + .expression = expr.expression, + } }; + }, + else => @compileError(""), + }; + + @compileError("Unexpected end of template"); + } +} + +fn parseArgDeref(comptime tokens: TokenIter) ExpressionResult { + 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, + .expression = .{ .arg_deref = fields }, + } else @compileError("Unexpected token"), + } + _ = iter.next(); + } + } +} + +const Expression = union(enum) { + arg_deref: []const []const u8, +}; + +const ExpressionResult = struct { + new_iter: TokenIter, + expression: Expression, +}; + +const Statement = union(enum) { + expression: Expression, + for_loop: struct { + subtemplate: []const u8, + indexable: Expression, + iteration_capture: []const u8, + index_capture: ?[]const u8, + }, +}; + +const StatementResult = struct { + new_iter: TokenIter, + statement: Statement, +}; + const State = enum { text, text_close_bracket, @@ -68,12 +146,17 @@ const State = enum { 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 { @@ -96,6 +179,7 @@ const TokenIter = struct { '{' => 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) {} @@ -105,7 +189,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 9c2ce7b..503b619 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -1,7 +1,7 @@ - { .community.name } + {.community.name} @@ -9,10 +9,11 @@

{{ REAL BRACKETS }}

+ {{#for .community}} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

{{#template note_display ($note)}} - {{/for}} + {{#end}}
From d24957b7a02b8965867651a2b71871f974424680 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 22:11:16 -0800 Subject: [PATCH 03/12] Create ParseResult --- src/template/lib.zig | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 7cf23d6..1d4f830 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -20,7 +20,7 @@ pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !v _ = comptime iter.next(); } else { const result = comptime parseStatement(iter); - try executeStatement(writer, result.statement, args); + try executeStatement(writer, result.item, args); iter = result.new_iter; } }, @@ -54,7 +54,7 @@ fn argDeref(writer: anytype, comptime names: []const []const u8, arg: anytype) ! return argDeref(writer, names[1..], @field(arg, names[0])); } -fn parseStatement(comptime tokens: TokenIter) StatementResult { +fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { comptime { var iter = tokens; while (iter.next()) |token| switch (token) { @@ -77,8 +77,8 @@ fn parseStatement(comptime tokens: TokenIter) StatementResult { .close_bracket => break, else => @compileError("TODO"), }; - return .{ .new_iter = iter, .statement = .{ - .expression = expr.expression, + return .{ .new_iter = iter, .item = .{ + .expression = .{ .arg_deref = expr.item }, } }; }, else => @compileError(""), @@ -88,7 +88,7 @@ fn parseStatement(comptime tokens: TokenIter) StatementResult { } } -fn parseArgDeref(comptime tokens: TokenIter) ExpressionResult { +fn parseArgDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; @@ -107,7 +107,7 @@ fn parseArgDeref(comptime tokens: TokenIter) ExpressionResult { }, else => if (wants == .period) return .{ .new_iter = iter, - .expression = .{ .arg_deref = fields }, + .item = fields, } else @compileError("Unexpected token"), } _ = iter.next(); @@ -115,15 +115,17 @@ fn parseArgDeref(comptime tokens: TokenIter) ExpressionResult { } } +fn ParseResult(comptime T: type) type { + return struct { + new_iter: TokenIter, + item: T, + }; +} + const Expression = union(enum) { arg_deref: []const []const u8, }; -const ExpressionResult = struct { - new_iter: TokenIter, - expression: Expression, -}; - const Statement = union(enum) { expression: Expression, for_loop: struct { @@ -134,11 +136,6 @@ const Statement = union(enum) { }, }; -const StatementResult = struct { - new_iter: TokenIter, - statement: Statement, -}; - const State = enum { text, text_close_bracket, From efb50a325b140efb1dc5d77c90fdd60c990b3264 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 22:35:27 -0800 Subject: [PATCH 04/12] Further split parsing and execution --- src/template/lib.zig | 83 ++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 1d4f830..a35cc20 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -7,32 +7,15 @@ pub fn main() !void { const logging = false; pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { - @setEvalBranchQuota(@intCast(u32, template.len * 6)); - comptime var iter = TokenIter{ .text = template }; - inline while (comptime iter.next()) |token| { - if (logging) @compileLog(token); - switch (token) { - .text => |text| try writer.writeAll(text), - .open_bracket => { - const next = comptime iter.peek() orelse @compileError("Unexpected end of template"); - if (next == .open_bracket) { - try writer.writeByte('{'); - _ = comptime iter.next(); - } else { - const result = comptime parseStatement(iter); - try executeStatement(writer, result.item, args); - iter = result.new_iter; - } - }, - .close_bracket => { - const next = comptime iter.next() orelse @compileError("Unexpected end of template"); - if (comptime next == .close_bracket) try writer.writeByte('}') else @compileError("Unpaired close bracket, did you mean \"}}\"?"); - }, - .whitespace => |wsp| try writer.writeAll(wsp), - .period => try writer.writeByte('.'), - .pound => try writer.writeByte('#'), - } - } + 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 { @@ -54,6 +37,49 @@ fn argDeref(writer: anytype, comptime names: []const []const u8, arg: anytype) ! 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; @@ -122,6 +148,11 @@ fn ParseResult(comptime T: type) type { }; } +const TemplateItem = union(enum) { + text: []const u8, + statement: Statement, +}; + const Expression = union(enum) { arg_deref: []const []const u8, }; From 934296b38461eb37759b731fdfe0d96362edcf08 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 23:16:46 -0800 Subject: [PATCH 05/12] Parse for loop --- src/template/lib.zig | 121 +++++++++++++++++++++++++++++-------- src/template/test.tmp.html | 3 +- 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index a35cc20..4ab839c 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -7,8 +7,9 @@ pub fn main() !void { 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); + @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) !void { @@ -23,6 +24,9 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype) !v .expression => |expr| switch (expr) { .arg_deref => |fields| try argDeref(writer, fields, args), }, + .for_loop => { + try writer.writeAll("For loop"); + }, else => @compileError("TODO"), } } @@ -37,14 +41,18 @@ fn argDeref(writer: anytype, comptime names: []const []const u8, arg: anytype) ! return argDeref(writer, names[1..], @field(arg, names[0])); } -fn parseTemplate(comptime template: []const u8) []const TemplateItem { +const TemplateType = enum { + root, + subtemplate, +}; + +fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateType) ParseResult([]const TemplateItem) { comptime { - @setEvalBranchQuota(@intCast(u32, template.len * 6)); - var iter = TokenIter{ .text = template }; + var iter = tokens; var items: []const TemplateItem = &.{}; var current_text: []const u8 = ""; - while (iter.next()) |token| { + parse_loop: while (iter.next()) |token| { if (logging) @compileLog(token); switch (token) { .whitespace, .text => |text| current_text = current_text ++ text, @@ -60,6 +68,9 @@ fn parseTemplate(comptime template: []const u8) []const TemplateItem { } const result = parseStatement(iter); 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 }}; } }, @@ -76,40 +87,94 @@ fn parseTemplate(comptime template: []const u8) []const TemplateItem { items = items ++ [_]TemplateItem{.{ .text = current_text }}; } - return items; + return .{ + .new_iter = iter, + .item = items, + }; } } fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { comptime { var iter = tokens; - while (iter.next()) |token| switch (token) { + var stmt: Statement = 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"); + switch (keyword) { + .end_for => break .{ .end_for = {} }, + .@"for" => { + const result = parseForLoop(iter); + // statemnt already finished so just return + return .{ + .new_iter = result.new_iter, + .item = .{ .for_loop = result.item }, + }; + }, + + //else => @compileError("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 }, - } }; + break .{ .expression = .{ .arg_deref = expr.item } }; }, else => @compileError(""), }; + // 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)); + }, + }; + + @compileError("Unexpected end of template"); + } +} + +fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { + comptime { + const indexable = parseExpression(tokens); + var iter = indexable.new_iter; + while (iter.next()) |token| switch (token) { + .whitespace => {}, + .close_bracket => break, + else => @compileError("Unexpected token"), + }; + + const subtemplate = parseTemplate(iter, .subtemplate); + + return .{ .new_iter = subtemplate.new_iter, .item = .{ + .indexable = indexable.item, + .subtemplate = subtemplate.item, + } }; + } +} + +fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { + comptime { + var iter = tokens; + while (iter.next()) |token| switch (token) { + .whitespace => {}, + .period => { + const deref = parseArgDeref(iter); + return .{ .new_iter = deref.new_iter, .item = .{ .arg_deref = deref.item } }; + }, + else => @compileError("Expected Expression"), + }; + @compileError("Unexpected end of template"); } } @@ -157,14 +222,15 @@ const Expression = union(enum) { arg_deref: []const []const u8, }; +const ForLoop = struct { + subtemplate: []const TemplateItem, + indexable: Expression, +}; + const Statement = union(enum) { expression: Expression, - for_loop: struct { - subtemplate: []const u8, - indexable: Expression, - iteration_capture: []const u8, - index_capture: ?[]const u8, - }, + for_loop: ForLoop, + end_for: void, }; const State = enum { @@ -176,6 +242,7 @@ const State = enum { const Keyword = enum { @"for", + end_for, }; const Token = union(enum) { @@ -192,6 +259,8 @@ const TokenIter = struct { text: []const u8, peeked_token: ?Token = null, + row: usize = 0, + fn next(self: *TokenIter) ?Token { if (self.peeked_token) |token| { self.peeked_token = null; @@ -211,6 +280,8 @@ const TokenIter = struct { ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} + const newline_count = std.mem.count(u8, remaining[0..idx], "\n"); + self.row += newline_count; self.start += idx - 1; return .{ .whitespace = remaining[0..idx] }; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 503b619..30e772a 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,7 +9,8 @@

{{ REAL BRACKETS }}

- {{#for .community}} + {#for .community} + {#end_for} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

{{#template note_display ($note)}} From dcf777f5ea4b9261510a079901e0ee7c5ffaeec7 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 23:39:36 -0800 Subject: [PATCH 06/12] Iterate over for loop --- src/template/lib.zig | 52 +++++++++++++++++++++++++++----------- src/template/test.tmp.html | 2 +- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 4ab839c..3e014e5 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -1,7 +1,10 @@ const std = @import("std"); pub fn main() !void { - try execute(std.io.getStdOut().writer(), @embedFile("./test.tmp.html"), .{ .community = .{ .name = "abcd" } }); + try execute(std.io.getStdOut().writer(), @embedFile("./test.tmp.html"), .{ + .community = .{ .name = "abcd" }, + .foo = [_][]const u8{ "5", "4", "3", "2", "1" }, + }); } const logging = false; @@ -22,23 +25,42 @@ fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, 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), + .arg_deref => |fields| { + const arg = argDeref(args, fields); + try print(writer, arg); + }, }, - .for_loop => { - try writer.writeAll("For loop"); + .for_loop => |loop| { + const fields = loop.iterable.arg_deref; + const iterable = argDeref(args, fields); + for (iterable) |str| { + try writer.writeAll(str); + } }, 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}); - } +fn print(writer: anytype, arg: anytype) !void { + if (comptime std.meta.trait.isZigString(@TypeOf(arg))) return writer.writeAll(arg); - return argDeref(writer, names[1..], @field(arg, names[0])); + @compileError("TODO"); +} + +fn ArgDeref(comptime T: type, comptime names: []const []const u8) type { + if (names.len == 0) return T; + + // Compiler segfaults when I use std.meta to get this info so we search it manually + const field = for (@typeInfo(T).Struct.fields) |f| { + if (std.mem.eql(u8, f.name, names[0])) break f; + } else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T)); + + return ArgDeref(field.field_type, names[1..]); +} + +fn argDeref(arg: anytype, comptime names: []const []const u8) ArgDeref(@TypeOf(arg), names) { + if (names.len == 0) return arg; + return argDeref(@field(arg, names[0]), names[1..]); } const TemplateType = enum { @@ -146,8 +168,8 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { comptime { - const indexable = parseExpression(tokens); - var iter = indexable.new_iter; + const iterable = parseExpression(tokens); + var iter = iterable.new_iter; while (iter.next()) |token| switch (token) { .whitespace => {}, .close_bracket => break, @@ -157,7 +179,7 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { const subtemplate = parseTemplate(iter, .subtemplate); return .{ .new_iter = subtemplate.new_iter, .item = .{ - .indexable = indexable.item, + .iterable = iterable.item, .subtemplate = subtemplate.item, } }; } @@ -224,7 +246,7 @@ const Expression = union(enum) { const ForLoop = struct { subtemplate: []const TemplateItem, - indexable: Expression, + iterable: Expression, }; const Statement = union(enum) { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 30e772a..c00d1c4 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,7 +9,7 @@

{{ REAL BRACKETS }}

- {#for .community} + {#for .foo} {#end_for} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

From 9d74bce266090fc3f5caa954adb44c62c8163be5 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 15 Nov 2022 23:53:29 -0800 Subject: [PATCH 07/12] Add loop captures --- src/template/lib.zig | 72 ++++++++++++++++++++++++++++---------- src/template/test.tmp.html | 3 +- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 3e014e5..07a93b5 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -33,8 +33,10 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype) !v .for_loop => |loop| { const fields = loop.iterable.arg_deref; const iterable = argDeref(args, fields); - for (iterable) |str| { - try writer.writeAll(str); + const subtemplate = loop.subtemplate; + try writer.writeAll("capture on " ++ loop.capture ++ ": "); + for (iterable) |_| { + try executeTemplate(writer, subtemplate, args); } }, else => @compileError("TODO"), @@ -102,6 +104,8 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp }, .period => current_text = current_text ++ ".", .pound => current_text = current_text ++ "#", + .pipe => current_text = current_text ++ "|", + .dollar => current_text = current_text ++ "$", } } @@ -166,22 +170,56 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { } } +fn skipWhitespace(comptime tokens: TokenIter) TokenIter { + comptime { + var iter = tokens; + while (iter.peek()) |token| switch (token) { + .whitespace => _ = iter.next(), + else => break, + }; + + return iter; + } +} + +fn endStatement(comptime tokens: TokenIter) TokenIter { + comptime { + var iter = skipWhitespace(tokens); + + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .close_bracket) @compileError("Unexpected token"); + return iter; + } +} + fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { comptime { const iterable = parseExpression(tokens); var iter = iterable.new_iter; - while (iter.next()) |token| switch (token) { - .whitespace => {}, - .close_bracket => break, - else => @compileError("Unexpected token"), + + iter = skipWhitespace(iter); + { + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .pipe) @compileError("Unexpected token"); + } + { + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .dollar) @compileError("Unexpected token"); + } + const capture = blk: { + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .text) @compileError("Unexpected token"); + break :blk token.text; }; + { + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .pipe) @compileError("Unexpected token"); + } + iter = endStatement(iter); const subtemplate = parseTemplate(iter, .subtemplate); - return .{ .new_iter = subtemplate.new_iter, .item = .{ - .iterable = iterable.item, - .subtemplate = subtemplate.item, - } }; + return .{ .new_iter = subtemplate.new_iter, .item = .{ .iterable = iterable.item, .subtemplate = subtemplate.item, .capture = capture } }; } } @@ -247,6 +285,7 @@ const Expression = union(enum) { const ForLoop = struct { subtemplate: []const TemplateItem, iterable: Expression, + capture: []const u8, }; const Statement = union(enum) { @@ -255,13 +294,6 @@ const Statement = union(enum) { end_for: void, }; -const State = enum { - text, - text_close_bracket, - template_start, - template, -}; - const Keyword = enum { @"for", end_for, @@ -274,6 +306,8 @@ const Token = union(enum) { period: void, whitespace: []const u8, pound: void, + pipe: void, + dollar: void, }; const TokenIter = struct { @@ -299,6 +333,8 @@ const TokenIter = struct { '}' => return .{ .close_bracket = {} }, '.' => return .{ .period = {} }, '#' => return .{ .pound = {} }, + '|' => return .{ .pipe = {} }, + '$' => return .{ .dollar = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} @@ -310,7 +346,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 c00d1c4..2f39f2a 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,8 +9,7 @@

{{ REAL BRACKETS }}

- {#for .foo} - {#end_for} + {#for .foo |$f|}a testing thing {#end_for} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

{{#template note_display ($note)}} From 8ebe77541f3c13cdc29b52bf4db96836bab0668b Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 16 Nov 2022 00:01:45 -0800 Subject: [PATCH 08/12] Add capture syntax --- src/template/lib.zig | 49 +++++++++++++++++++++++++------------- src/template/test.tmp.html | 1 + 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 07a93b5..dd48587 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -12,31 +12,40 @@ const logging = false; pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { @setEvalBranchQuota(@intCast(u32, template.len * 6)); const tmpl = comptime parseTemplate(TokenIter{ .text = template }, .root); - try executeTemplate(writer, tmpl.item, args); + try executeTemplate(writer, tmpl.item, args, .{ .bar = "abc" }); } -fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: anytype) !void { +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), + .statement => |stmt| try executeStatement(writer, stmt, args, captures), }; } -fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype) !void { +fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { switch (stmt) { .expression => |expr| switch (expr) { .arg_deref => |fields| { - const arg = argDeref(args, fields); + const arg = deref(args, fields); + try print(writer, arg); + }, + .capture_deref => |fields| { + const arg = deref(captures, fields); try print(writer, arg); }, }, .for_loop => |loop| { const fields = loop.iterable.arg_deref; - const iterable = argDeref(args, fields); + const iterable = deref(args, fields); const subtemplate = loop.subtemplate; try writer.writeAll("capture on " ++ loop.capture ++ ": "); for (iterable) |_| { - try executeTemplate(writer, subtemplate, args); + try executeTemplate( + writer, + subtemplate, + args, + captures, + ); } }, else => @compileError("TODO"), @@ -49,7 +58,7 @@ fn print(writer: anytype, arg: anytype) !void { @compileError("TODO"); } -fn ArgDeref(comptime T: type, comptime names: []const []const u8) type { +fn Deref(comptime T: type, comptime names: []const []const u8) type { if (names.len == 0) return T; // Compiler segfaults when I use std.meta to get this info so we search it manually @@ -57,12 +66,12 @@ fn ArgDeref(comptime T: type, comptime names: []const []const u8) type { if (std.mem.eql(u8, f.name, names[0])) break f; } else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T)); - return ArgDeref(field.field_type, names[1..]); + return Deref(field.field_type, names[1..]); } -fn argDeref(arg: anytype, comptime names: []const []const u8) ArgDeref(@TypeOf(arg), names) { +fn deref(arg: anytype, comptime names: []const []const u8) Deref(@TypeOf(arg), names) { if (names.len == 0) return arg; - return argDeref(@field(arg, names[0]), names[1..]); + return deref(@field(arg, names[0]), names[1..]); } const TemplateType = enum { @@ -146,9 +155,14 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { } }, .period => { - const expr = parseArgDeref(iter); - iter = expr.new_iter; - break .{ .expression = .{ .arg_deref = expr.item } }; + 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 => @compileError(""), }; @@ -229,8 +243,8 @@ fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { while (iter.next()) |token| switch (token) { .whitespace => {}, .period => { - const deref = parseArgDeref(iter); - return .{ .new_iter = deref.new_iter, .item = .{ .arg_deref = deref.item } }; + const names = parseDeref(iter); + return .{ .new_iter = names.new_iter, .item = .{ .arg_deref = names.item } }; }, else => @compileError("Expected Expression"), }; @@ -239,7 +253,7 @@ fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { } } -fn parseArgDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { +fn parseDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { comptime { var iter = tokens; var fields: []const []const u8 = &.{}; @@ -280,6 +294,7 @@ const TemplateItem = union(enum) { const Expression = union(enum) { arg_deref: []const []const u8, + capture_deref: []const []const u8, }; const ForLoop = struct { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 2f39f2a..7b7e597 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,6 +9,7 @@

{{ REAL BRACKETS }}

+ {$bar} {#for .foo |$f|}a testing thing {#end_for} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

From 61493dc7973bac9d0abbfe30861337aa6d75eb3a Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 16 Nov 2022 00:06:53 -0800 Subject: [PATCH 09/12] Combine Expressions and statements --- src/template/lib.zig | 56 ++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index dd48587..b52f8b5 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -99,7 +99,7 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp items = items ++ [_]TemplateItem{.{ .text = current_text }}; current_text = ""; } - const result = parseStatement(iter); + 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"); @@ -129,12 +129,16 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp } } -fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { +fn parseExpressionOrStatement( + comptime tokens: TokenIter, + comptime as_statement: bool, +) ParseResult(if (as_statement) Statement else Expression) { comptime { var iter = tokens; 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; @@ -164,23 +168,25 @@ fn parseStatement(comptime tokens: TokenIter) ParseResult(Statement) { iter = names.new_iter; break .{ .expression = .{ .capture_deref = names.item } }; }, - else => @compileError(""), + else => if (as_statement) @compileError("TODO") else break, }; - // 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)); - }, - }; + 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)); + }, + }; - @compileError("Unexpected end of template"); + @compileError("Unexpected end of template"); + } else return .{ .new_iter = iter, .item = stmt.expression }; } } @@ -208,7 +214,7 @@ fn endStatement(comptime tokens: TokenIter) TokenIter { 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); @@ -237,22 +243,6 @@ fn parseForLoop(comptime tokens: TokenIter) ParseResult(ForLoop) { } } -fn parseExpression(comptime tokens: TokenIter) ParseResult(Expression) { - comptime { - var iter = tokens; - while (iter.next()) |token| switch (token) { - .whitespace => {}, - .period => { - const names = parseDeref(iter); - return .{ .new_iter = names.new_iter, .item = .{ .arg_deref = names.item } }; - }, - else => @compileError("Expected Expression"), - }; - - @compileError("Unexpected end of template"); - } -} - fn parseDeref(comptime tokens: TokenIter) ParseResult([]const []const u8) { comptime { var iter = tokens; From c9d0090ab2a550fd112bdeef8399442b9631f5fe Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 16 Nov 2022 00:18:14 -0800 Subject: [PATCH 10/12] Add loop captures to capture struct --- src/template/lib.zig | 27 +++++++++++++++++++++++++-- src/template/test.tmp.html | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index b52f8b5..83617dd 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -39,12 +39,12 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca const iterable = deref(args, fields); const subtemplate = loop.subtemplate; try writer.writeAll("capture on " ++ loop.capture ++ ": "); - for (iterable) |_| { + for (iterable) |v| { try executeTemplate( writer, subtemplate, args, - captures, + addCapture(captures, loop.capture, v), ); } }, @@ -74,6 +74,29 @@ fn deref(arg: anytype, comptime names: []const []const u8) Deref(@TypeOf(arg), n return deref(@field(arg, names[0]), names[1..]); } +fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type) type { + var fields = std.meta.fields(Root) ++ [_]std.builtin.Type.StructField{.{ + .name = name, + .field_type = Val, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf(Val), + }}; + + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); +} + +fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture(@TypeOf(root), name, @TypeOf(val)) { + var result = std.mem.zeroInit(AddCapture(@TypeOf(root), name, @TypeOf(val)), root); + @field(result, name) = val; + return result; +} + const TemplateType = enum { root, subtemplate, diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 7b7e597..08b8dfb 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -10,7 +10,7 @@
{$bar} - {#for .foo |$f|}a testing thing {#end_for} + {#for .foo |$f|}a testing thing: {$f} {#end_for} {{#for args.notes |$note, $i|}}

Note no. {{$i}}

{{#template note_display ($note)}} From e4a04b869e6f5a1c4452f2773f4c19bb7325d6a2 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 16 Nov 2022 00:33:06 -0800 Subject: [PATCH 11/12] refactor --- src/template/lib.zig | 44 ++++++++++++++++++++++++-------------- src/template/test.tmp.html | 8 ++----- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 83617dd..c997629 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -4,15 +4,17 @@ pub fn main() !void { try execute(std.io.getStdOut().writer(), @embedFile("./test.tmp.html"), .{ .community = .{ .name = "abcd" }, .foo = [_][]const u8{ "5", "4", "3", "2", "1" }, + .baz = [_][]const []const u8{ + &.{ "5", "4", "3", "2", "1" }, + &.{ "5", "4", "3", "2", "1" }, + }, }); } -const logging = false; - pub fn execute(writer: anytype, comptime template: []const u8, args: anytype) !void { @setEvalBranchQuota(@intCast(u32, template.len * 6)); const tmpl = comptime parseTemplate(TokenIter{ .text = template }, .root); - try executeTemplate(writer, tmpl.item, args, .{ .bar = "abc" }); + try executeTemplate(writer, tmpl.item, args, .{}); } fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: anytype, captures: anytype) !void { @@ -24,21 +26,13 @@ fn executeTemplate(writer: anytype, comptime items: []const TemplateItem, args: fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, captures: anytype) !void { switch (stmt) { - .expression => |expr| switch (expr) { - .arg_deref => |fields| { - const arg = deref(args, fields); - try print(writer, arg); - }, - .capture_deref => |fields| { - const arg = deref(captures, fields); - try print(writer, arg); - }, + .expression => |expr| { + const val = evaluateExpression(expr, args, captures); + try print(writer, val); }, .for_loop => |loop| { - const fields = loop.iterable.arg_deref; - const iterable = deref(args, fields); + const iterable = evaluateExpression(loop.iterable, args, captures); const subtemplate = loop.subtemplate; - try writer.writeAll("capture on " ++ loop.capture ++ ": "); for (iterable) |v| { try executeTemplate( writer, @@ -54,6 +48,7 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca fn print(writer: anytype, arg: anytype) !void { if (comptime std.meta.trait.isZigString(@TypeOf(arg))) return writer.writeAll(arg); + @compileLog(@TypeOf(arg)); @compileError("TODO"); } @@ -74,6 +69,24 @@ fn deref(arg: anytype, comptime names: []const []const u8) Deref(@TypeOf(arg), n return deref(@field(arg, names[0]), names[1..]); } +fn EvaluateExpression(comptime expression: Expression, comptime Args: type, comptime Captures: type) type { + return switch (expression) { + .arg_deref => |names| Deref(Args, names), + .capture_deref => |names| Deref(Captures, names), + }; +} + +fn evaluateExpression( + comptime expression: Expression, + args: anytype, + captures: anytype, +) EvaluateExpression(expression, @TypeOf(args), @TypeOf(captures)) { + return switch (expression) { + .arg_deref => |names| deref(args, names), + .capture_deref => |names| deref(captures, names), + }; +} + fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type) type { var fields = std.meta.fields(Root) ++ [_]std.builtin.Type.StructField{.{ .name = name, @@ -109,7 +122,6 @@ fn parseTemplate(comptime tokens: TokenIter, comptime template_type: TemplateTyp var current_text: []const u8 = ""; parse_loop: while (iter.next()) |token| { - if (logging) @compileLog(token); switch (token) { .whitespace, .text => |text| current_text = current_text ++ text, .open_bracket => { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 08b8dfb..87bac71 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -9,12 +9,8 @@

{{ REAL BRACKETS }}

- {$bar} - {#for .foo |$f|}a testing thing: {$f} {#end_for} - {{#for args.notes |$note, $i|}} -

Note no. {{$i}}

- {{#template note_display ($note)}} - {{#end}} + {#for .baz |$f|}{#for $f |$b|}{$b}:{#end_for} + {#end_for}
From 305c5f8c92d73c737b03d9034978d8d14715d2a0 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 16 Nov 2022 02:23:14 -0800 Subject: [PATCH 12/12] Support for nested structs in sql layer --- src/api/lib.zig | 2 +- src/api/services/actors.zig | 7 ++++ src/api/services/notes.zig | 19 +++++++-- src/sql/lib.zig | 77 +++++++++++++++++++++++++++++++------ 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/src/api/lib.zig b/src/api/lib.zig index ab19097..4b2a434 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -85,7 +85,7 @@ pub const TimelineArgs = struct { }; pub const TimelineResult = struct { - items: []services.notes.Note, + items: []services.notes.NoteDetailed, prev_page: TimelineArgs, next_page: TimelineArgs, diff --git a/src/api/services/actors.zig b/src/api/services/actors.zig index 2a7f964..30dd6d2 100644 --- a/src/api/services/actors.zig +++ b/src/api/services/actors.zig @@ -13,6 +13,13 @@ pub const CreateError = error{ DatabaseFailure, }; +pub const ActorDetailed = struct { + id: Uuid, + username: []const u8, + host: []const u8, + created_at: DateTime, +}; + pub const LookupError = error{ DatabaseFailure, }; diff --git a/src/api/services/notes.zig b/src/api/services/notes.zig index 086a85a..f413f76 100644 --- a/src/api/services/notes.zig +++ b/src/api/services/notes.zig @@ -14,6 +14,17 @@ pub const Note = struct { created_at: DateTime, }; +pub const NoteDetailed = struct { + id: Uuid, + + author: struct { + id: Uuid, + username: []const u8, + }, + content: []const u8, + created_at: DateTime, +}; + pub const CreateError = error{ DatabaseFailure, }; @@ -81,7 +92,7 @@ pub const QueryArgs = struct { }; pub const QueryResult = struct { - items: []Note, + items: []NoteDetailed, prev_page: QueryArgs, next_page: QueryArgs, @@ -91,7 +102,9 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul var builder = sql.QueryBuilder.init(alloc); defer builder.deinit(); - try builder.appendSlice(selectStarFromNote ++ + try builder.appendSlice( + \\SELECT note.id, note.content, note.created_at, actor.id AS "author.id", actor.username AS "author.username" + \\FROM note \\ JOIN actor ON actor.id = note.author_id \\ ); @@ -140,7 +153,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul }; const results = try db.queryRowsWithOptions( - Note, + NoteDetailed, try builder.terminate(), query_args, max_items, diff --git a/src/sql/lib.zig b/src/sql/lib.zig index 9b1f599..88c2bf1 100644 --- a/src/sql/lib.zig +++ b/src/sql/lib.zig @@ -120,12 +120,60 @@ const RawResults = union(Engine) { } }; +fn FieldPtr(comptime Ptr: type, comptime names: []const []const u8) type { + if (names.len == 0) return Ptr; + + const T = std.meta.Child(Ptr); + + const field = for (@typeInfo(T).Struct.fields) |f| { + if (std.mem.eql(u8, f.name, names[0])) break f; + } else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T)); + + return FieldPtr(*field.field_type, names[1..]); +} + +fn fieldPtr(ptr: anytype, comptime names: []const []const u8) FieldPtr(@TypeOf(ptr), names) { + if (names.len == 0) return ptr; + + return fieldPtr(&@field(ptr.*, names[0]), names[1..]); +} + +fn isScalar(comptime T: type) bool { + if (comptime std.meta.trait.isZigString(T)) return true; + if (comptime std.meta.trait.isIntegral(T)) return true; + if (comptime std.meta.trait.isFloat(T)) return true; + if (comptime std.meta.trait.is(.Enum)(T)) return true; + if (T == bool) return true; + if (comptime std.meta.trait.hasFn("parse")(T)) return true; + + if (comptime std.meta.trait.is(.Optional)(T) and isScalar(std.meta.Child(T))) return true; + + return false; +} + +fn recursiveFieldPaths(comptime T: type, comptime prefix: []const []const u8) []const []const []const u8 { + comptime { + var fields: []const []const []const u8 = &.{}; + + for (std.meta.fields(T)) |f| { + const full_name = prefix ++ [_][]const u8{f.name}; + if (isScalar(f.field_type)) { + fields = fields ++ [_][]const []const u8{full_name}; + } else { + fields = fields ++ recursiveFieldPaths(f.field_type, full_name); + } + } + + return fields; + } +} + // Represents a set of results. // row() must be called until it returns null, or the query may not complete // Must be deallocated by a call to finish() pub fn Results(comptime T: type) type { // would normally make this a declaration of the struct, but it causes the compiler to crash - const fields = if (T == void) .{} else std.meta.fields(T); + const fields = if (T == void) .{} else recursiveFieldPaths(T, &.{}); return struct { const Self = @This(); @@ -141,13 +189,16 @@ pub fn Results(comptime T: type) type { return Self{ .underlying = underlying, .column_indices = blk: { var indices: [fields.len]u15 = undefined; inline for (fields) |f, i| { - indices[i] = if (!std.meta.trait.isTuple(T)) - underlying.columnIndex(f.name) catch { - std.log.err("Could not find column index for field {s}", .{f.name}); + if (comptime std.meta.trait.isTuple(T)) { + indices[i] = i; + } else { + const name = util.comptimeJoin(".", f); + indices[i] = + underlying.columnIndex(name) catch { + std.log.err("Could not find column index for field {s}", .{name}); return error.ColumnMismatch; - } - else - i; + }; + } } break :blk indices; } }; @@ -168,15 +219,19 @@ pub fn Results(comptime T: type) type { // Iteration bounds must be defined at comptime (inline for) but the number of fields we could // successfully allocate is defined at runtime. So we iterate over the entire field array and // conditionally deallocate fields in the loop. - if (i < fields_allocated) util.deepFree(alloc, @field(result, f.name)); + const ptr = fieldPtr(&result, f); + if (i < fields_allocated) util.deepFree(alloc, ptr.*); }; inline for (fields) |f, i| { // TODO: Causes compiler segfault. why? //const F = f.field_type; - const F = @TypeOf(@field(result, f.name)); - @field(result, f.name) = row_val.get(F, self.column_indices[i], alloc) catch |err| { - std.log.err("SQL: Error getting column {s} of type {}", .{ f.name, F }); + //const F = @TypeOf(@field(result, f.name)); + const F = std.meta.Child(FieldPtr(*@TypeOf(result), f)); + const ptr = fieldPtr(&result, f); + const name = util.comptimeJoin(".", f); + ptr.* = row_val.get(F, self.column_indices[i], alloc) catch |err| { + std.log.err("SQL: Error getting column {s} of type {}", .{ name, F }); return err; }; fields_allocated += 1;