// #target: macos-x64 <* @require Type.kindof == STRUCT *> module abc(); import std::io; import std::collections::list; def TextTagList = List(); fault TextError { UNTERMINATED_TAG, EMPTY_TAG, MISSING_TAG, UNSUPPORTED_TAG, } enum TextTagKind: char { STRING, TEMPLATE, } struct TextTemplate { Allocator allocator; String template; TextTag[] tags; Type data; } struct TextTag { usz start; usz end; TextTagKind kind; union { String* data; TextTemplate* template; } } <* @require self.tags.len == 0 "template already initialized" @require tag_start != tag_end *> fn void! TextTemplate.init(&self, String template, String tag_start = "{{", String tag_end = "}}", Allocator using = allocator::heap()) { TextTagList tags; String tmpl = template; uptr data = (uptr)&self.data; while (true) { usz! start = tmpl.index_of(tag_start); if (catch start) break; tmpl = tmpl[start + tag_start.len..]; usz! end = tmpl.index_of(tag_end); if (catch end) return TextError.UNTERMINATED_TAG?; String name = tmpl[:end].trim(); if (name == "") return TextError.EMPTY_TAG?; // Check that the tag exists in the data struct. TextTag tag = {| $foreach ($m : Type.membersof) if (name == $m.nameof) { $switch ($m.typeid) $case String.typeid: return TextTag{ .kind = STRING, .data = (String*)(data + $m.offsetof), }; $default: $if $defined($m.get(self.data).as_stream): return TextTag{ .kind = TEMPLATE, .template = self.data.$eval($m.nameof).as_stream(), }; $endif $endswitch //return TextError.UNSUPPORTED_TAG?; } $endforeach return TextError.MISSING_TAG?; |}!; tmpl = tmpl[end + tag_end.len..]; tag.start = start; tag.end = start + tag_start.len + end + tag_end.len; tags.push(tag); } *self = { .allocator = using, .template = template, .tags = tags.array_view() }; } fn void! TextTemplate.free(&self) { allocator::free(self.allocator, self.tags); *self = {}; } fn usz! TextTemplate.write_to(&self, OutStream writer) { usz n; usz pos; foreach (tag : self.tags) { n += writer.write(self.template[pos:tag.start])!; pos += tag.end; n += tag.write(writer)!; } n += writer.write(self.template[pos..])!; return n; } fn usz! TextTag.write(&self, OutStream writer) { switch (self.kind) { case STRING: return writer.write(*self.data); case TEMPLATE: return self.template.write_to(writer); } } module text_test; import abc; import std::io; def Tmpl = TextTemplate(); struct Data { String user; String world; bool ok; } def FooTmpl = TextTemplate(); def BarTmpl = TextTemplate(); struct Foo { String foo; BarTmpl* bar; } struct Bar { String bar; } fn void main() { String foo_tmpl = "<<{{foo}} | {{bar}}>>"; FooTmpl ft; ft.init(foo_tmpl, using: allocator::temp())!!; defer ft.free()!!; } /* #expect: text_test.ll define void @text_test.main() #0 { entry: %foo_tmpl = alloca %"char[]", align 8 %ft = alloca %TextTemplate, align 8 %error_var = alloca i64, align 8 %indirectarg = alloca %"char[]", align 8 %indirectarg1 = alloca %any, align 8 %varargslots = alloca [1 x %any], align 16 %indirectarg2 = alloca %"any[]", align 8 %error_var3 = alloca i64, align 8 %varargslots8 = alloca [1 x %any], align 16 %indirectarg10 = alloca %"any[]", align 8 store %"char[]" { ptr @.str, i64 21 }, ptr %foo_tmpl, align 8 call void @llvm.memset.p0.i64(ptr align 8 %ft, i8 0, i64 72, i1 false) %0 = load ptr, ptr @std.core.mem.allocator.thread_temp_allocator, align 8 %i2nb = icmp eq ptr %0, null br i1 %i2nb, label %if.then, label %if.exit if.then: ; preds = %entry call void @std.core.mem.allocator.init_default_temp_allocators() br label %if.exit if.exit: ; preds = %if.then, %entry %1 = load ptr, ptr @std.core.mem.allocator.thread_temp_allocator, align 8 %2 = insertvalue %any undef, ptr %1, 0 %3 = insertvalue %any %2, i64 ptrtoint (ptr @"$ct.std.core.mem.allocator.TempAllocator" to i64), 1 %lo = load ptr, ptr %foo_tmpl, align 8 %ptradd = getelementptr inbounds i8, ptr %foo_tmpl, i64 8 %hi = load i64, ptr %ptradd, align 8 store %"char[]" { ptr @.str.2, i64 2 }, ptr %indirectarg, align 8 store %any %3, ptr %indirectarg1, align 8 %4 = call i64 @"abc$text_test.Foo$.TextTemplate.init"(ptr %ft, ptr %lo, i64 %hi, ptr @.str.1, i64 2, ptr byval(%"char[]") align 8 %indirectarg, ptr byval(%any) align 8 %indirectarg1) %not_err = icmp eq i64 %4, 0 %5 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) br i1 %5, label %after_check, label %assign_optional assign_optional: ; preds = %if.exit store i64 %4, ptr %error_var, align 8 br label %panic_block after_check: ; preds = %if.exit br label %noerr_block panic_block: ; preds = %assign_optional %6 = insertvalue %any undef, ptr %error_var, 0 %7 = insertvalue %any %6, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 store %any %7, ptr %varargslots, align 16 %8 = insertvalue %"any[]" undef, ptr %varargslots, 0 %"$$temp" = insertvalue %"any[]" %8, i64 1, 1 store %"any[]" %"$$temp", ptr %indirectarg2, align 8 call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36, ptr @.file, i64 25, ptr @.func, i64 4, i32 161, ptr byval(%"any[]") align 8 %indirectarg2) unreachable noerr_block: ; preds = %after_check %9 = call i64 @"abc$text_test.Foo$.TextTemplate.free"(ptr %ft) %not_err4 = icmp eq i64 %9, 0 %10 = call i1 @llvm.expect.i1(i1 %not_err4, i1 true) br i1 %10, label %after_check6, label %assign_optional5 assign_optional5: ; preds = %noerr_block store i64 %9, ptr %error_var3, align 8 br label %panic_block7 after_check6: ; preds = %noerr_block br label %noerr_block11 panic_block7: ; preds = %assign_optional5 %11 = insertvalue %any undef, ptr %error_var3, 0 %12 = insertvalue %any %11, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 store %any %12, ptr %varargslots8, align 16 %13 = insertvalue %"any[]" undef, ptr %varargslots8, 0 %"$$temp9" = insertvalue %"any[]" %13, i64 1, 1 store %"any[]" %"$$temp9", ptr %indirectarg10, align 8 call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36, ptr @.file, i64 25, ptr @.func, i64 4, i32 162, ptr byval(%"any[]") align 8 %indirectarg10) unreachable noerr_block11: ; preds = %after_check6 ret void }