// #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(self.data.$eval($m.nameof).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 i64 @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 %error_var2 = alloca i64, align 8 %varargslots = alloca [1 x %"any*"], align 16 %indirectarg6 = 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 %not = icmp eq ptr %0, null br i1 %not, 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 %guard_block after_check: ; preds = %if.exit br label %noerr_block guard_block: ; preds = %assign_optional %6 = load i64, ptr %error_var, align 8 ret i64 %6 noerr_block: ; preds = %after_check %7 = call i64 @"abc$text_test.Foo$.TextTemplate.free"(ptr %ft) %not_err3 = icmp eq i64 %7, 0 %8 = call i1 @llvm.expect.i1(i1 %not_err3, i1 true) br i1 %8, label %after_check5, label %assign_optional4 assign_optional4: ; preds = %noerr_block store i64 %7, ptr %error_var2, align 8 br label %panic_block after_check5: ; preds = %noerr_block br label %noerr_block7 panic_block: ; preds = %assign_optional4 %9 = insertvalue %"any*" undef, ptr %error_var2, 0 %10 = insertvalue %"any*" %9, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 store %"any*" %10, ptr %varargslots, align 16 %11 = insertvalue %"any*[]" undef, ptr %varargslots, 0 %"$$temp" = insertvalue %"any*[]" %11, i64 1, 1 store %"any*[]" %"$$temp", ptr %indirectarg6, align 8 call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36, ptr @.file, i64 25 unreachable noerr_block7: ; preds = %after_check5 ret i64 0 }