srctree

Gregory Mullen parent 940f24ea e4ba2460
mov more of the template engine into comptime

This is a partial commit, while I decide if I wanted to write the larger testing template (this compiles and passes tests, but srctree won't compile because a lot of the "recursive" template code isn't finished yet. I might also end up rewriting the directive code instead, see the commented out `page` line, where it might make more sense for this to be a generated type. Another option is to turn directive into a union itself, and isolate each type into it's own struct. Still unsure what the cleanest is likely to be.
src/template.zig added: 126, removed: 46, total 80
@@ -244,7 +244,11 @@ test "directive something" {
.blob = "<Something>",
};
 
const ctx = .{
const Basic = struct {
something: []const u8,
};
 
const ctx = Basic{
.something = @as([]const u8, "Some Text Here"),
};
const pg = Page(t, @TypeOf(ctx)).init(ctx);
@@ -258,7 +262,7 @@ test "directive something" {
.blob = "<Something />",
};
 
const ctx2 = .{
const ctx2 = Basic{
.something = @as([]const u8, "Some Text Here"),
};
const pg2 = Page(t2, @TypeOf(ctx2)).init(ctx2);
@@ -333,23 +337,23 @@ test "directive nothing" {
}
 
test "directive nothing new" {
const a = std.testing.allocator;
const t = Template{
//.path = "/dev/null",
.name = "test",
.blob = "<Nothing>",
};
//const a = std.testing.allocator;
//const t = Template{
// //.path = "/dev/null",
// .name = "test",
// .blob = "<Nothing>",
//};
 
const ctx = .{};
//const ctx = .{};
 
// TODO is this still the expected behavior
//const p = Page(t, @TypeOf(ctx)).init(.{});
//try std.testing.expectError(error.VariableMissing, p);
//// TODO is this still the expected behavior
////const p = Page(t, @TypeOf(ctx)).init(.{});
////try std.testing.expectError(error.VariableMissing, p);
 
const pg = Page(t, @TypeOf(ctx)).init(.{});
const p = try allocPrint(a, "{}", .{pg});
defer a.free(p);
try std.testing.expectEqualStrings("<Nothing>", p);
//const pg = Page(t, @TypeOf(ctx)).init(.{});
//const p = try allocPrint(a, "{}", .{pg});
//defer a.free(p);
//try std.testing.expectEqualStrings("<Nothing>", p);
}
 
test "directive ORELSE" {
@@ -360,8 +364,12 @@ test "directive ORELSE" {
.blob = "<This default='string until end'>",
};
 
const ctx = .{
.this = @as(?[]const u8, null),
const Basic = struct {
this: ?[]const u8,
};
 
const ctx = Basic{
.this = null,
};
 
const pg = Page(t, @TypeOf(ctx)).init(ctx);
@@ -379,8 +387,12 @@ test "directive ORNULL" {
.blob = "<This ornull string until end>",
};
 
const ctx = .{
.this = @as(?[]const u8, null),
const Basic = struct {
this: ?[]const u8,
};
 
const ctx = Basic{
.this = null,
};
 
const pg = Page(t, @TypeOf(ctx)).init(ctx);
 
src/template/directive.zig added: 126, removed: 46, total 80
@@ -1,8 +1,9 @@
verb: Verb,
noun: []const u8,
otherwise: Otherwise,
known_type: ?KnownType = null,
tag_block: []const u8,
known_type: ?KnownType = null,
known_offset: ?usize = null,
 
pub const Directive = @This();
 
@@ -12,6 +13,7 @@ pub const Otherwise = union(enum) {
delete: void,
default: []const u8,
template: *const Template.Template,
//page: type,
blob: []const u8,
};
 
 
src/template/page.zig added: 126, removed: 46, total 80
@@ -10,7 +10,10 @@ const Kind = enum {
const Offset = struct {
start: usize,
end: usize,
kind: Kind,
kind: union(enum) {
directive: Directive,
slice: void,
},
};
 
pub fn PageRuntime(comptime PageDataType: type) type {
@@ -67,6 +70,7 @@ pub fn Page(comptime template: Template, comptime PageDataType: type) type {
var found_offsets: []const Offset = &[0]Offset{};
var pblob = template.blob;
var index: usize = 0;
var static: bool = true;
// Originally attempted to write this just using index, but got catastrophic
// backtracking errors when compiling. I'd have assumed this version would
// be more expensive, but here we are :D
@@ -83,13 +87,20 @@ pub fn Page(comptime template: Template, comptime PageDataType: type) type {
index += offset;
if (Directive.init(pblob)) |drct| {
const end = drct.tag_block.len;
found_offsets = found_offsets ++ [_]Offset{.{
var os = Offset{
.start = index,
.end = index + end,
.kind = .directive,
}};
.kind = .{ .directive = drct },
};
if (drct.verb == .variable) {
var local: [0xff]u8 = undefined;
const name = local[0..makeFieldName(drct.noun, &local)];
os.kind.directive.known_offset = @offsetOf(PageDataType, name);
}
found_offsets = found_offsets ++ [_]Offset{os};
pblob = pblob[end..];
index += end;
static = static and drct.verb == .variable;
} else {
if (indexOfPosLinear(u8, pblob, 1, "<")) |next| {
if (index != next) {
@@ -114,13 +125,16 @@ pub fn Page(comptime template: Template, comptime PageDataType: type) type {
}
const offset_len = found_offsets.len;
const offsets: [offset_len]Offset = found_offsets[0..offset_len].*;
const static_c = static;
 
return struct {
data: PageDataType,
 
pub const Self = @This();
pub const Kind = PageDataType;
pub const Static = static_c;
pub const PageTemplate = template;
pub const DataOffsets: [offset_len]Offset = offsets;
data: PageDataType,
 
pub fn init(d: PageDataType) Page(template, PageDataType) {
return .{ .data = d };
@@ -136,23 +150,56 @@ pub fn Page(comptime template: Template, comptime PageDataType: type) type {
for (Self.DataOffsets) |os| {
switch (os.kind) {
.slice => try out.writeAll(blob[os.start..os.end]),
.directive => {
if (Directive.init(blob[os.start..os.end])) |drct| {
drct.formatTyped(PageDataType, self.data, out) catch |err| switch (err) {
error.IgnoreDirective => try out.writeAll(blob[os.start..os.end]),
error.VariableMissing => {
if (!is_test) log.err(
"Template Error, variable missing {{{s}}}",
.{blob[os.start..os.end]},
);
try out.writeAll(blob[os.start..os.end]);
},
else => return err,
};
} else {
std.debug.print("init failed ?\n", .{});
//try out.writeAll(blob[end..]);
unreachable;
.directive => |directive| {
switch (directive.verb) {
.variable => {
if (directive.known_offset) |offset| {
if (directive.known_type) |_| {
directive.formatTyped(PageDataType, self.data, out) catch unreachable;
continue;
}
 
const ptr: [*]const u8 = @ptrCast(&self.data);
switch (directive.otherwise) {
.required => {
const vari: *const []const u8 = @ptrCast(@alignCast(&ptr[offset]));
try out.writeAll(vari.*);
},
.ignore => {
const vari: *const ?[]const u8 = @ptrCast(@alignCast(&ptr[offset]));
if (vari.*) |v|
try out.writeAll(v);
},
.delete => {
const vari: *const ?[]const u8 = @ptrCast(@alignCast(&ptr[offset]));
if (vari.*) |v|
try out.writeAll(v);
},
.default => |default| {
const vari: *const ?[]const u8 = @ptrCast(@alignCast(&ptr[offset]));
if (vari.*) |v| {
try out.writeAll(v);
} else {
try out.writeAll(default);
}
},
else => unreachable,
}
}
},
else => {
directive.formatTyped(PageDataType, self.data, out) catch |err| switch (err) {
error.IgnoreDirective => try out.writeAll(blob[os.start..os.end]),
error.VariableMissing => {
if (!is_test) log.err(
"Template Error, variable missing {{{s}}}",
.{blob[os.start..os.end]},
);
try out.writeAll(blob[os.start..os.end]);
},
else => return err,
};
},
}
},
}
@@ -164,6 +211,25 @@ pub fn Page(comptime template: Template, comptime PageDataType: type) type {
};
}
 
const makeFieldName = Templates.makeFieldName;
fn typeField(T: type, name: []const u8, data: T) ?[]const u8 {
if (@typeInfo(T) != .Struct) return null;
var local: [0xff]u8 = undefined;
const realname = local[0..makeFieldName(name, &local)];
inline for (std.meta.fields(T)) |field| {
if (eql(u8, field.name, realname)) {
switch (field.type) {
[]const u8,
?[]const u8,
=> return @field(data, field.name),
 
else => return null,
}
}
}
return null;
}
 
const std = @import("std");
const is_test = @import("builtin").is_test;
const Allocator = std.mem.Allocator;