libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
variantlookup.h
Go to the documentation of this file.
1
9#pragma once
10
11
12#include <promeki/config.h>
13#if PROMEKI_ENABLE_CORE
14#include <cstdlib>
15#include <functional>
16#include <optional>
17#include <type_traits>
18#include <typeinfo>
19#include <promeki/optional.h>
20#include <promeki/function.h>
21#include <promeki/namespace.h>
22#include <promeki/string.h>
23#include <promeki/stringlist.h>
24#include <promeki/map.h>
26#include <promeki/variant.h>
27#include <promeki/variantspec.h>
29#include <promeki/error.h>
30#include <promeki/fnv1a.h>
32#include <promeki/util.h>
33
34PROMEKI_NAMESPACE_BEGIN
35
36namespace detail {
37
53 template <typename T, typename = void> struct HasVariantLookupDispatch : std::false_type {};
54
55 template <typename T>
56 struct HasVariantLookupDispatch<T, std::void_t<decltype(std::declval<const T &>().variantLookupResolve(
57 std::declval<const String &>(), std::declval<Error *>()))>>
58 : std::true_type {};
59
63 template <typename T> inline constexpr bool hasVariantLookupDispatchV = HasVariantLookupDispatch<T>::value;
64
79 struct VariantLookupSegment {
81 String name;
83 String rest;
85 size_t index = 0;
87 bool hasIndex = false;
89 bool hasRest = false;
90 };
91
108 inline bool parseLeadingSegment(const String &key, VariantLookupSegment &out, Error *err = nullptr) {
109 if (err != nullptr) *err = Error::Ok;
110 const size_t len = key.byteCount();
111 if (len == 0) {
112 if (err != nullptr) *err = Error::ParseFailed;
113 return false;
114 }
115 const char *s = key.cstr();
116 size_t i = 0;
117 auto isNameChar = [](char c) -> bool {
118 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' ||
119 c == '@';
120 };
121 while (i < len && isNameChar(s[i])) ++i;
122 if (i == 0) {
123 if (err != nullptr) *err = Error::ParseFailed;
124 return false;
125 }
126 out.name = String(s, i);
127 out.hasIndex = false;
128 out.hasRest = false;
129 out.rest = String();
130 out.index = 0;
131
132 if (i < len && s[i] == '[') {
133 ++i;
134 const size_t numStart = i;
135 while (i < len && s[i] >= '0' && s[i] <= '9') ++i;
136 if (i == numStart) {
137 if (err != nullptr) *err = Error::ParseFailed;
138 return false;
139 }
140 if (i >= len || s[i] != ']') {
141 if (err != nullptr) *err = Error::ParseFailed;
142 return false;
143 }
144 char *endp = nullptr;
145 out.index = static_cast<size_t>(std::strtoull(s + numStart, &endp, 10));
146 out.hasIndex = true;
147 ++i;
148 }
149
150 if (i == len) return true;
151
152 if (s[i] != '.') {
153 if (err != nullptr) *err = Error::ParseFailed;
154 return false;
155 }
156 ++i;
157 if (i >= len) {
158 if (err != nullptr) *err = Error::ParseFailed;
159 return false;
160 }
161 out.rest = String(s + i, len - i);
162 out.hasRest = true;
163 return true;
164 }
165
188 inline const VariantSpec *walkSpecPath(const VariantSpec *cur, const String &path, Error *err = nullptr) {
189 if (cur == nullptr) {
190 if (err != nullptr) *err = Error::IdNotFound;
191 return nullptr;
192 }
193 if (err != nullptr) *err = Error::Ok;
194 String remaining = path;
195 while (!remaining.isEmpty() && cur != nullptr) {
196 VariantLookupSegment seg;
197 // parseLeadingSegment requires a leading name; we
198 // accept paths that start with a bracket (root is the
199 // list) by handling that case explicitly.
200 bool bracketStart = (remaining[0] == '[');
201 if (bracketStart) {
202 // Index-only step: e.g. "[3]" or "[3].rest".
203 size_t end = 1;
204 const char *p = remaining.cstr();
205 const size_t len = remaining.byteCount();
206 while (end < len && p[end] >= '0' && p[end] <= '9') ++end;
207 if (end == 1 || end >= len || p[end] != ']') {
208 if (err != nullptr) *err = Error::ParseFailed;
209 return nullptr;
210 }
211 cur = cur->elementSpec();
212 if (cur == nullptr) {
213 if (err != nullptr) *err = Error::IdNotFound;
214 return nullptr;
215 }
216 ++end; // past ']'
217 if (end == len) {
218 remaining = String();
219 } else if (p[end] == '.') {
220 remaining = remaining.mid(end + 1);
221 } else {
222 if (err != nullptr) *err = Error::ParseFailed;
223 return nullptr;
224 }
225 continue;
226 }
227 if (!parseLeadingSegment(remaining, seg, err)) return nullptr;
228 // Name step always descends via valueSpec. If the
229 // segment also has an index, descend once more via
230 // elementSpec — matches the runtime resolver's
231 // (map → list) traversal for "name[N]" segments.
232 cur = cur->valueSpec();
233 if (cur == nullptr) {
234 if (err != nullptr) *err = Error::IdNotFound;
235 return nullptr;
236 }
237 if (seg.hasIndex) {
238 cur = cur->elementSpec();
239 if (cur == nullptr) {
240 if (err != nullptr) *err = Error::IdNotFound;
241 return nullptr;
242 }
243 }
244 remaining = seg.hasRest ? seg.rest : String();
245 }
246 return cur;
247 }
248
249} // namespace detail
250
374template <typename T> class VariantLookup {
375 public:
376 // ============================================================
377 // Key
378 // ============================================================
379
397 class Key {
398 public:
400 constexpr Key() = default;
401
414 static constexpr Key literal(const char *name) { return Key(fnv1a(name)); }
415
423 static Key find(const String &name) {
424 const Registry &r = registry();
425 ReadWriteLock::ReadLocker lock(r.lock);
426 uint64_t id = r.findId(name);
427 return id == Invalid ? Key() : Key(id);
428 }
429
436 static constexpr Key fromId(uint64_t id) { return Key(id); }
437
439 constexpr uint64_t id() const { return _id; }
440
452 String name() const {
453 const Registry &r = registry();
454 ReadWriteLock::ReadLocker lock(r.lock);
455 auto it = r.names.find(_id);
456 if (it == r.names.end()) return String();
457 return it->second;
458 }
459
461 constexpr bool isValid() const { return _id != Invalid; }
462
463 constexpr bool operator==(const Key &other) const { return _id == other._id; }
464 constexpr bool operator!=(const Key &other) const { return _id != other._id; }
465 constexpr bool operator<(const Key &other) const { return _id < other._id; }
466
467 private:
468 static constexpr uint64_t Invalid = UINT64_MAX;
469 uint64_t _id = Invalid;
470
471 constexpr explicit Key(uint64_t id) : _id(id) {}
472
473 friend class VariantLookup<T>;
474 };
475
476 // ============================================================
477 // Handler signatures
478 // ============================================================
479
481 using ScalarGet = Function<Optional<Variant>(const T &)>;
491 using ScalarSet = Function<Error(T &, const Variant &)>;
493 using IndexedScalarGet = Function<Optional<Variant>(const T &, size_t)>;
495 using IndexedScalarSet = Function<Error(T &, size_t, const Variant &)>;
496
498 using ComposeResolve = Function<Optional<Variant>(const T &, const String &, Error *)>;
500 using ComposeAssign = Function<bool(T &, const String &, const Variant &, Error *)>;
501
503 using IndexedCompResolve =
504 Function<Optional<Variant>(const T &, size_t, const String &, Error *)>;
506 using IndexedCompAssign = Function<bool(T &, size_t, const String &, const Variant &, Error *)>;
507
519 using ComposeSpecFor = Function<const VariantSpec *(const String &, Error *)>;
520
532 using VariantTreeGet = Function<Variant(const T &)>;
533
534 // ============================================================
535 // Registrar (fluent builder)
536 // ============================================================
537
554 class Registrar {
555 public:
557 Registrar scalar(const String &name, ScalarGet get,
558 const VariantSpec *spec = nullptr) const {
559 Registry &r = registry();
560 ReadWriteLock::WriteLocker lock(r.lock);
561 uint64_t id = r.declareName(name);
562 r.scalars.insert(id, ScalarEntry{std::move(get), ScalarSet(), spec});
563 return *this;
564 }
565
567 Registrar scalar(const String &name, ScalarGet get, ScalarSet set,
568 const VariantSpec *spec = nullptr) const {
569 Registry &r = registry();
570 ReadWriteLock::WriteLocker lock(r.lock);
571 uint64_t id = r.declareName(name);
572 r.scalars.insert(id, ScalarEntry{std::move(get), std::move(set), spec});
573 return *this;
574 }
575
577 Registrar indexedScalar(const String &name, IndexedScalarGet get,
578 const VariantSpec *spec = nullptr) const {
579 Registry &r = registry();
580 ReadWriteLock::WriteLocker lock(r.lock);
581 uint64_t id = r.declareName(name);
582 r.indexedScalars.insert(
583 id, IndexedScalarEntry{std::move(get), IndexedScalarSet(), spec});
584 return *this;
585 }
586
588 Registrar indexedScalar(const String &name, IndexedScalarGet get, IndexedScalarSet set,
589 const VariantSpec *spec = nullptr) const {
590 Registry &r = registry();
591 ReadWriteLock::WriteLocker lock(r.lock);
592 uint64_t id = r.declareName(name);
593 r.indexedScalars.insert(
594 id, IndexedScalarEntry{std::move(get), std::move(set), spec});
595 return *this;
596 }
597
616 template <typename U>
617 Registrar child(const String &name, Function<const U *(const T &)> get,
618 Function<U *(T &)> getMut = nullptr) const {
619 ComposeResolve resolveFn = [get](const T &t, const String &rest,
620 Error *err) -> Optional<Variant> {
621 const U *u = get(t);
622 if (u == nullptr) {
623 if (err != nullptr) *err = Error::IdNotFound;
624 return std::nullopt;
625 }
626 return VariantLookup<U>::resolve(*u, rest, err);
627 };
628 ComposeAssign assignFn;
629 if (getMut) {
630 assignFn = [getMut](T &t, const String &rest, const Variant &v,
631 Error *err) -> bool {
632 U *u = getMut(t);
633 if (u == nullptr) {
634 if (err != nullptr) *err = Error::IdNotFound;
635 return false;
636 }
637 return VariantLookup<U>::assign(*u, rest, v, err);
638 };
639 }
640 ComposeSpecFor specFn = [](const String &rest,
641 Error *err) -> const VariantSpec * {
642 return VariantLookup<U>::specFor(rest, err);
643 };
644 Function<StringList(const T &, const String &)> dumpFn =
645 [get](const T &t, const String &indent) -> StringList {
646 const U *u = get(t);
647 if (u == nullptr) return StringList();
648 return VariantLookup<U>::dump(*u, indent);
649 };
650 Registry &r = registry();
651 ReadWriteLock::WriteLocker lock(r.lock);
652 uint64_t id = r.declareName(name);
653 r.children.insert(id, ComposeEntry{std::move(resolveFn), std::move(assignFn),
654 std::move(specFn), std::move(dumpFn)});
655 return *this;
656 }
657
668 template <typename U>
669 Registrar indexedChild(const String &name,
670 Function<const U *(const T &, size_t)> get,
671 Function<U *(T &, size_t)> getMut = nullptr) const {
672 IndexedCompResolve resolveFn = [get](const T &t, size_t idx, const String &rest,
673 Error *err) -> Optional<Variant> {
674 const U *u = get(t, idx);
675 if (u == nullptr) {
676 if (err != nullptr) *err = Error::OutOfRange;
677 return std::nullopt;
678 }
679 return VariantLookup<U>::resolve(*u, rest, err);
680 };
681 IndexedCompAssign assignFn;
682 if (getMut) {
683 assignFn = [getMut](T &t, size_t idx, const String &rest,
684 const Variant &v, Error *err) -> bool {
685 U *u = getMut(t, idx);
686 if (u == nullptr) {
687 if (err != nullptr) *err = Error::OutOfRange;
688 return false;
689 }
690 return VariantLookup<U>::assign(*u, rest, v, err);
691 };
692 }
693 ComposeSpecFor specFn = [](const String &rest,
694 Error *err) -> const VariantSpec * {
695 return VariantLookup<U>::specFor(rest, err);
696 };
697 Function<StringList(const T &, const String &)> dumpFn =
698 [get](const T &t, const String &indent) -> StringList {
699 // Probe 0..N, stopping at the first
700 // null returned from @c get. Each
701 // slice gets its own @c "[i]:"
702 // header so multi-slice sections
703 // don't smoosh into one flat block
704 // in @ref dump output.
705 StringList out;
706 const String childIndent = indent + " ";
707 for (size_t i = 0;; ++i) {
708 const U *u = get(t, i);
709 if (u == nullptr) break;
710 StringList sub = VariantLookup<U>::dump(*u, childIndent);
711 out.pushToBack(indent + String::sprintf("[%zu]:", i));
712 for (const String &l : sub) out.pushToBack(l);
713 }
714 return out;
715 };
716 Registry &r = registry();
717 ReadWriteLock::WriteLocker lock(r.lock);
718 uint64_t id = r.declareName(name);
719 r.indexedChildren.insert(
720 id, IndexedCompEntry{std::move(resolveFn), std::move(assignFn),
721 std::move(specFn), std::move(dumpFn)});
722 return *this;
723 }
724
758 template <typename U>
759 Registrar
760 indexedChildByValue(const String &name,
761 Function<Optional<U>(const T &, size_t)> get) const {
762 IndexedCompResolve resolveFn = [get](const T &t, size_t idx, const String &rest,
763 Error *err) -> Optional<Variant> {
764 auto u = get(t, idx);
765 if (!u.hasValue()) {
766 if (err != nullptr) *err = Error::OutOfRange;
767 return std::nullopt;
768 }
769 return VariantLookup<U>::resolve(*u, rest, err);
770 };
771 // No assign — by-value temporaries have no backing store.
772 IndexedCompAssign assignFn;
773 ComposeSpecFor specFn = [](const String &rest,
774 Error *err) -> const VariantSpec *{
775 return VariantLookup<U>::specFor(rest, err);
776 };
777 Function<StringList(const T &, const String &)> dumpFn =
778 [get](const T &t, const String &indent) -> StringList {
779 // Matches the pointer-variant
780 // @ref indexedChild dump format —
781 // one @c "[i]:" header per slice,
782 // scalars indented underneath.
783 StringList out;
784 const String childIndent = indent + " ";
785 for (size_t i = 0;; ++i) {
786 auto u = get(t, i);
787 if (!u.hasValue()) break;
788 StringList sub = VariantLookup<U>::dump(*u, childIndent);
789 out.pushToBack(indent + String::sprintf("[%zu]:", i));
790 for (const String &l : sub) out.pushToBack(l);
791 }
792 return out;
793 };
794 Registry &r = registry();
795 ReadWriteLock::WriteLocker lock(r.lock);
796 uint64_t id = r.declareName(name);
797 r.indexedChildren.insert(
798 id, IndexedCompEntry{std::move(resolveFn), std::move(assignFn),
799 std::move(specFn), std::move(dumpFn)});
800 return *this;
801 }
802
825 template <CompiledString DbName>
826 Registrar
827 database(const String &prefix,
828 Function<const VariantDatabase<DbName> *(const T &)> get,
829 Function<VariantDatabase<DbName> *(T &)> getMut = nullptr) const {
830 ComposeResolve resolveFn = [get](const T &t, const String &rest,
831 Error *err) -> Optional<Variant> {
832 const VariantDatabase<DbName> *db = get(t);
833 if (db == nullptr) {
834 if (err != nullptr) *err = Error::IdNotFound;
835 return std::nullopt;
836 }
837 auto id = VariantDatabase<DbName>::ID::find(rest);
838 if (!id.isValid() || !db->contains(id)) {
839 if (err != nullptr) *err = Error::IdNotFound;
840 return std::nullopt;
841 }
842 if (err != nullptr) *err = Error::Ok;
843 return db->get(id);
844 };
845 ComposeAssign assignFn;
846 if (getMut) {
847 assignFn = [getMut](T &t, const String &rest, const Variant &v,
848 Error *err) -> bool {
849 VariantDatabase<DbName> *db = getMut(t);
850 if (db == nullptr) {
851 if (err != nullptr) *err = Error::IdNotFound;
852 return false;
853 }
854 typename VariantDatabase<DbName>::ID id(rest);
855 if (!db->set(id, v)) {
856 if (err != nullptr) *err = Error::ConversionFailed;
857 return false;
858 }
859 if (err != nullptr) *err = Error::Ok;
860 return true;
861 };
862 }
863 ComposeSpecFor specFn = [](const String &rest,
864 Error *err) -> const VariantSpec * {
865 const VariantSpec *sp = VariantDatabase<DbName>::specFor(rest);
866 if (sp == nullptr && err != nullptr) *err = Error::IdNotFound;
867 return sp;
868 };
869 Function<StringList(const T &, const String &)> dumpFn =
870 [get](const T &t, const String &indent) -> StringList {
871 const VariantDatabase<DbName> *db = get(t);
872 StringList out;
873 if (db == nullptr) return out;
874 // Match Metadata::dump formatting
875 // so db-like bindings render the
876 // same shape regardless of which
877 // concrete VariantDatabase is
878 // bound — consumers reading the
879 // output don't have to
880 // special-case the Meta prefix.
881 db->forEach([&out, &indent](typename VariantDatabase<DbName>::ID id,
882 const Variant &value) {
883 String s = indent;
884 s += id.name();
885 s += " [";
886 s += value.typeName();
887 s += "]: ";
888 s += value.format(String());
889 out.pushToBack(s);
890 });
891 return out;
892 };
893 Registry &r = registry();
894 ReadWriteLock::WriteLocker lock(r.lock);
895 uint64_t id = r.declareName(prefix);
896 r.databases.insert(id, ComposeEntry{std::move(resolveFn), std::move(assignFn),
897 std::move(specFn), std::move(dumpFn)});
898 return *this;
899 }
900
949 Registrar variantTree(const String &name, VariantTreeGet get,
950 const VariantSpec *spec = nullptr) const {
951 // Resolve a "Name.rest" lookup: borrow the
952 // Variant from the user, descend via the
953 // shared tree-walker. Empty rest returns
954 // the whole Variant, with IdNotFound when
955 // the getter signalled "not present"
956 // (invalid Variant).
957 ComposeResolve resolveFn = [get](const T &t, const String &rest,
958 Error *err) -> Optional<Variant> {
959 Variant root = get(t);
960 if (!root.isValid()) {
961 if (err != nullptr) *err = Error::IdNotFound;
962 return std::nullopt;
963 }
964 if (rest.isEmpty()) return root;
965 Error pe;
966 Variant out = promekiResolveVariantPath(root, rest, &pe);
967 if (pe.isError()) {
968 if (err != nullptr) *err = pe;
969 return std::nullopt;
970 }
971 return out;
972 };
973 // Resolve a "Name[N].rest" lookup: glue the
974 // index back into the path string and reuse
975 // promekiResolveVariantPath.
976 IndexedCompResolve indexedResolveFn = [get](const T &t, size_t idx,
977 const String &rest,
978 Error *err) -> Optional<Variant> {
979 Variant root = get(t);
980 if (!root.isValid()) {
981 if (err != nullptr) *err = Error::IdNotFound;
982 return std::nullopt;
983 }
984 String path = String::sprintf("[%zu]", idx);
985 if (!rest.isEmpty()) path += "." + rest;
986 Error pe;
987 Variant out = promekiResolveVariantPath(root, path, &pe);
988 if (pe.isError()) {
989 if (err != nullptr) *err = pe;
990 return std::nullopt;
991 }
992 return out;
993 };
994 // specFor walks the spec's element/value
995 // subtree to mirror what
996 // promekiResolveVariantPath did at runtime.
997 ComposeSpecFor specFn = [spec](const String &rest,
998 Error *err) -> const VariantSpec * {
999 if (spec == nullptr) {
1000 if (err != nullptr) *err = Error::IdNotFound;
1001 return nullptr;
1002 }
1003 if (rest.isEmpty()) return spec;
1004 return detail::walkSpecPath(spec, rest, err);
1005 };
1006 // Indexed entry's specFor: an extra
1007 // [N] prefix vs. the named entry.
1008 ComposeSpecFor indexedSpecFn = [spec](const String &rest,
1009 Error *err) -> const VariantSpec * {
1010 if (spec == nullptr) {
1011 if (err != nullptr) *err = Error::IdNotFound;
1012 return nullptr;
1013 }
1014 String path("[0]"); // index value irrelevant for spec walk
1015 if (!rest.isEmpty()) path += "." + rest;
1016 return detail::walkSpecPath(spec, path, err);
1017 };
1018 Function<StringList(const T &, const String &)> dumpFn =
1019 [get](const T &t, const String &indent) -> StringList {
1020 StringList out;
1021 Variant root = get(t);
1022 if (!root.isValid()) return out;
1023 String s = indent;
1024 s += "[";
1025 s += root.typeName();
1026 s += "]: ";
1027 s += root.format(String());
1028 out.pushToBack(s);
1029 return out;
1030 };
1031 Registry &r = registry();
1032 ReadWriteLock::WriteLocker lock(r.lock);
1033 uint64_t id = r.declareName(name);
1034 r.variantTrees.insert(id, ComposeEntry{std::move(resolveFn), ComposeAssign(),
1035 std::move(specFn), dumpFn});
1036 // Indexed access (Name[N]) lives in a parallel
1037 // entry so the dispatcher's existing
1038 // seg.hasIndex branch lands here rather than
1039 // forcing an indexedChild lookup. The
1040 // indexed dump shape is empty because the
1041 // base entry already prints the whole tree.
1042 r.indexedVariantTrees.insert(
1043 id, IndexedCompEntry{std::move(indexedResolveFn), IndexedCompAssign(),
1044 std::move(indexedSpecFn),
1045 Function<StringList(const T &, const String &)>()});
1046 return *this;
1047 }
1048
1068 template <typename Base> Registrar inheritsFrom() const {
1069 static_assert(std::is_base_of_v<Base, T>,
1070 "VariantLookup::inheritsFrom<Base>(): "
1071 "Base must be a public base of T");
1072 static_assert(!std::is_same_v<Base, T>, "VariantLookup::inheritsFrom<Base>(): "
1073 "Base must differ from T");
1074 Registry &r = registry();
1075 ReadWriteLock::WriteLocker lock(r.lock);
1076 r.inherit.resolve = [](const T &t, const String &key,
1077 Error *err) -> Optional<Variant> {
1078 return VariantLookup<Base>::resolveDirect(static_cast<const Base &>(t),
1079 key, err);
1080 };
1081 r.inherit.assign = [](T &t, const String &key, const Variant &v,
1082 Error *err) -> bool {
1083 return VariantLookup<Base>::assignDirect(static_cast<Base &>(t), key, v,
1084 err);
1085 };
1086 r.inherit.specFor = [](const String &key, Error *err) -> const VariantSpec * {
1087 return VariantLookup<Base>::specForDirect(key, err);
1088 };
1089 r.inherit.resolveByKey = [](const T &t, uint64_t id,
1090 Error *err) -> Optional<Variant> {
1091 return VariantLookup<Base>::resolveKeyIdDirect(
1092 static_cast<const Base &>(t), id, err);
1093 };
1094 r.inherit.assignByKey = [](T &t, uint64_t id, const Variant &v,
1095 Error *err) -> bool {
1096 return VariantLookup<Base>::assignKeyIdDirect(static_cast<Base &>(t),
1097 id, v, err);
1098 };
1099 r.inherit.specForByKey = [](uint64_t id, Error *err) -> const VariantSpec * {
1100 return VariantLookup<Base>::specForKeyIdDirect(id, err);
1101 };
1102 r.inherit.resolveIndexedByKey = [](const T &t, uint64_t id, size_t index,
1103 Error *err) -> Optional<Variant> {
1104 return VariantLookup<Base>::resolveIndexedKeyIdDirect(
1105 static_cast<const Base &>(t), id, index, err);
1106 };
1107 r.inherit.assignIndexedByKey = [](T &t, uint64_t id, size_t index,
1108 const Variant &v, Error *err) -> bool {
1109 return VariantLookup<Base>::assignIndexedKeyIdDirect(
1110 static_cast<Base &>(t), id, index, v, err);
1111 };
1112 r.inherit.forEachScalar = [](const Function<void(const String &)> &fn) {
1113 VariantLookup<Base>::forEachScalar(fn);
1114 };
1115 r.inherit.registeredScalars = []() {
1116 return VariantLookup<Base>::registeredScalars();
1117 };
1118 r.inherit.registeredIndexedScalars = []() {
1119 return VariantLookup<Base>::registeredIndexedScalars();
1120 };
1121 r.inherit.registeredChildren = []() {
1122 return VariantLookup<Base>::registeredChildren();
1123 };
1124 r.inherit.registeredIndexedChildren = []() {
1125 return VariantLookup<Base>::registeredIndexedChildren();
1126 };
1127 r.inherit.registeredDatabases = []() {
1128 return VariantLookup<Base>::registeredDatabases();
1129 };
1130 r.inherit.dumpComposites = [](const T &t, const String &indent) -> StringList {
1131 return VariantLookup<Base>::dumpComposites(static_cast<const Base &>(t),
1132 indent);
1133 };
1134 return *this;
1135 }
1136 };
1137
1139 static Registrar registrar() { return Registrar{}; }
1140
1141 // ============================================================
1142 // Dispatch
1143 // ============================================================
1144
1165 static Optional<Variant> resolve(const T &instance, const String &key, Error *err = nullptr) {
1166 if constexpr (detail::hasVariantLookupDispatchV<T>) {
1167 return instance.variantLookupResolve(key, err);
1168 } else {
1169 return resolveDirect(instance, key, err);
1170 }
1171 }
1172
1183 static Optional<Variant> resolveDirect(const T &instance, const String &key,
1184 Error *err = nullptr) {
1185 if (err != nullptr) *err = Error::Ok;
1186 detail::VariantLookupSegment seg;
1187 if (!detail::parseLeadingSegment(key, seg, err)) return std::nullopt;
1188
1189 const Registry &r = registry();
1190 ReadWriteLock::ReadLocker lock(r.lock);
1191 const uint64_t id = r.findId(seg.name);
1192 if (id == Registry::Invalid) {
1193 // Cascade to inherited registry before
1194 // reporting IdNotFound. The lock is
1195 // held but the inherit fallback touches
1196 // VariantLookup<Base>'s registry, which
1197 // has its own lock — no deadlock since
1198 // each Registry's lock is independent.
1199 if (r.inherit.resolve) {
1200 return r.inherit.resolve(instance, key, err);
1201 }
1202 if (err != nullptr) *err = Error::IdNotFound;
1203 return std::nullopt;
1204 }
1205
1206 if (!seg.hasRest) {
1207 if (seg.hasIndex) {
1208 auto it = r.indexedScalars.find(id);
1209 if (it != r.indexedScalars.end()) {
1210 auto v = it->second.get(instance, seg.index);
1211 if (!v.hasValue()) {
1212 if (err != nullptr) *err = Error::OutOfRange;
1213 }
1214 return v;
1215 }
1216 // Indexed access on a variantTree binding
1217 // (e.g. "Tags[2]") falls through to here.
1218 auto vtIt = r.indexedVariantTrees.find(id);
1219 if (vtIt != r.indexedVariantTrees.end()) {
1220 return vtIt->second.resolve(instance, seg.index, String(), err);
1221 }
1222 if (r.inherit.resolve) {
1223 return r.inherit.resolve(instance, key, err);
1224 }
1225 if (err != nullptr) *err = Error::IdNotFound;
1226 return std::nullopt;
1227 }
1228 auto it = r.scalars.find(id);
1229 if (it != r.scalars.end()) {
1230 auto v = it->second.get(instance);
1231 if (!v.hasValue()) {
1232 if (err != nullptr) *err = Error::IdNotFound;
1233 }
1234 return v;
1235 }
1236 // Terminal "Name" against a variantTree binding —
1237 // returns the whole tree as a Variant.
1238 auto vtIt = r.variantTrees.find(id);
1239 if (vtIt != r.variantTrees.end()) {
1240 return vtIt->second.resolve(instance, String(), err);
1241 }
1242 if (r.inherit.resolve) {
1243 return r.inherit.resolve(instance, key, err);
1244 }
1245 if (err != nullptr) *err = Error::IdNotFound;
1246 return std::nullopt;
1247 }
1248
1249 if (seg.hasIndex) {
1250 auto it = r.indexedChildren.find(id);
1251 if (it != r.indexedChildren.end()) {
1252 return it->second.resolve(instance, seg.index, seg.rest, err);
1253 }
1254 // "Name[N].rest" against a variantTree binding.
1255 auto vtIt = r.indexedVariantTrees.find(id);
1256 if (vtIt != r.indexedVariantTrees.end()) {
1257 return vtIt->second.resolve(instance, seg.index, seg.rest, err);
1258 }
1259 if (r.inherit.resolve) {
1260 return r.inherit.resolve(instance, key, err);
1261 }
1262 if (err != nullptr) *err = Error::IdNotFound;
1263 return std::nullopt;
1264 }
1265
1266 auto itChild = r.children.find(id);
1267 if (itChild != r.children.end()) {
1268 return itChild->second.resolve(instance, seg.rest, err);
1269 }
1270 auto itDb = r.databases.find(id);
1271 if (itDb != r.databases.end()) {
1272 return itDb->second.resolve(instance, seg.rest, err);
1273 }
1274 // "Name.rest" against a variantTree binding — descend into
1275 // the registered Variant via promekiResolveVariantPath.
1276 auto itVt = r.variantTrees.find(id);
1277 if (itVt != r.variantTrees.end()) {
1278 return itVt->second.resolve(instance, seg.rest, err);
1279 }
1280 if (r.inherit.resolve) {
1281 return r.inherit.resolve(instance, key, err);
1282 }
1283 if (err != nullptr) *err = Error::IdNotFound;
1284 return std::nullopt;
1285 }
1286
1297 static Optional<Variant> resolve(const T &instance, Key key, Error *err = nullptr) {
1298 if constexpr (detail::hasVariantLookupDispatchV<T>) {
1299 // Name lookup via Key is still handy
1300 // for dispatching types — route it
1301 // through the name path (most callers
1302 // will have the name the Key wraps) so
1303 // the virtual hook applies uniformly.
1304 String name = key.name();
1305 if (!name.isEmpty()) {
1306 return instance.variantLookupResolve(name, err);
1307 }
1308 return resolveKeyIdDirect(instance, key.id(), err);
1309 } else {
1310 return resolveKeyIdDirect(instance, key.id(), err);
1311 }
1312 }
1313
1320 static Optional<Variant> resolve(const T &instance, Key key, size_t index, Error *err = nullptr) {
1321 return resolveIndexedKeyIdDirect(instance, key.id(), index, err);
1322 }
1323
1333 static Optional<Variant> resolveKeyIdDirect(const T &instance, uint64_t id, Error *err = nullptr) {
1334 if (err != nullptr) *err = Error::Ok;
1335 const Registry &r = registry();
1336 ReadWriteLock::ReadLocker lock(r.lock);
1337 auto it = r.scalars.find(id);
1338 if (it == r.scalars.end()) {
1339 if (r.inherit.resolveByKey) {
1340 return r.inherit.resolveByKey(instance, id, err);
1341 }
1342 if (err != nullptr) *err = Error::IdNotFound;
1343 return std::nullopt;
1344 }
1345 auto v = it->second.get(instance);
1346 if (!v.hasValue() && err != nullptr) *err = Error::IdNotFound;
1347 return v;
1348 }
1349
1355 static Optional<Variant> resolveIndexedKeyIdDirect(const T &instance, uint64_t id, size_t index,
1356 Error *err = nullptr) {
1357 if (err != nullptr) *err = Error::Ok;
1358 const Registry &r = registry();
1359 ReadWriteLock::ReadLocker lock(r.lock);
1360 auto it = r.indexedScalars.find(id);
1361 if (it == r.indexedScalars.end()) {
1362 if (r.inherit.resolveIndexedByKey) {
1363 return r.inherit.resolveIndexedByKey(instance, id, index, err);
1364 }
1365 if (err != nullptr) *err = Error::IdNotFound;
1366 return std::nullopt;
1367 }
1368 auto v = it->second.get(instance, index);
1369 if (!v.hasValue() && err != nullptr) *err = Error::OutOfRange;
1370 return v;
1371 }
1372
1389 static bool assign(T &instance, const String &key, const Variant &value, Error *err = nullptr) {
1390 if constexpr (detail::hasVariantLookupDispatchV<T>) {
1391 return instance.variantLookupAssign(key, value, err);
1392 } else {
1393 return assignDirect(instance, key, value, err);
1394 }
1395 }
1396
1405 static bool assignDirect(T &instance, const String &key, const Variant &value, Error *err = nullptr) {
1406 if (err != nullptr) *err = Error::Ok;
1407 detail::VariantLookupSegment seg;
1408 if (!detail::parseLeadingSegment(key, seg, err)) return false;
1409
1410 const Registry &r = registry();
1411 ReadWriteLock::ReadLocker lock(r.lock);
1412 const uint64_t id = r.findId(seg.name);
1413 if (id == Registry::Invalid) {
1414 if (r.inherit.assign) {
1415 return r.inherit.assign(instance, key, value, err);
1416 }
1417 if (err != nullptr) *err = Error::IdNotFound;
1418 return false;
1419 }
1420
1421 if (!seg.hasRest) {
1422 if (seg.hasIndex) {
1423 auto it = r.indexedScalars.find(id);
1424 if (it != r.indexedScalars.end()) {
1425 if (!it->second.set) {
1426 if (err != nullptr) *err = Error::ReadOnly;
1427 return false;
1428 }
1429 Error setErr = it->second.set(instance, seg.index, value);
1430 if (setErr.isError()) {
1431 if (err != nullptr) *err = setErr;
1432 return false;
1433 }
1434 return true;
1435 }
1436 // Indexed assign on variantTree binding —
1437 // not supported (would require splicing the
1438 // value back into the tree).
1439 if (r.indexedVariantTrees.find(id) != r.indexedVariantTrees.end()) {
1440 if (err != nullptr) *err = Error::ReadOnly;
1441 return false;
1442 }
1443 if (r.inherit.assign) {
1444 return r.inherit.assign(instance, key, value, err);
1445 }
1446 if (err != nullptr) *err = Error::IdNotFound;
1447 return false;
1448 }
1449 auto it = r.scalars.find(id);
1450 if (it != r.scalars.end()) {
1451 if (!it->second.set) {
1452 if (err != nullptr) *err = Error::ReadOnly;
1453 return false;
1454 }
1455 Error setErr = it->second.set(instance, value);
1456 if (setErr.isError()) {
1457 if (err != nullptr) *err = setErr;
1458 return false;
1459 }
1460 return true;
1461 }
1462 if (r.variantTrees.find(id) != r.variantTrees.end()) {
1463 if (err != nullptr) *err = Error::ReadOnly;
1464 return false;
1465 }
1466 if (r.inherit.assign) {
1467 return r.inherit.assign(instance, key, value, err);
1468 }
1469 if (err != nullptr) *err = Error::IdNotFound;
1470 return false;
1471 }
1472
1473 if (seg.hasIndex) {
1474 auto it = r.indexedChildren.find(id);
1475 if (it != r.indexedChildren.end()) {
1476 if (!it->second.assign) {
1477 if (err != nullptr) *err = Error::ReadOnly;
1478 return false;
1479 }
1480 return it->second.assign(instance, seg.index, seg.rest, value, err);
1481 }
1482 if (r.indexedVariantTrees.find(id) != r.indexedVariantTrees.end()) {
1483 if (err != nullptr) *err = Error::ReadOnly;
1484 return false;
1485 }
1486 if (r.inherit.assign) {
1487 return r.inherit.assign(instance, key, value, err);
1488 }
1489 if (err != nullptr) *err = Error::IdNotFound;
1490 return false;
1491 }
1492
1493 auto itChild = r.children.find(id);
1494 if (itChild != r.children.end()) {
1495 if (!itChild->second.assign) {
1496 if (err != nullptr) *err = Error::ReadOnly;
1497 return false;
1498 }
1499 return itChild->second.assign(instance, seg.rest, value, err);
1500 }
1501 auto itDb = r.databases.find(id);
1502 if (itDb != r.databases.end()) {
1503 if (!itDb->second.assign) {
1504 if (err != nullptr) *err = Error::ReadOnly;
1505 return false;
1506 }
1507 return itDb->second.assign(instance, seg.rest, value, err);
1508 }
1509 if (r.variantTrees.find(id) != r.variantTrees.end()) {
1510 if (err != nullptr) *err = Error::ReadOnly;
1511 return false;
1512 }
1513 if (r.inherit.assign) {
1514 return r.inherit.assign(instance, key, value, err);
1515 }
1516 if (err != nullptr) *err = Error::IdNotFound;
1517 return false;
1518 }
1519
1523 static bool assign(T &instance, Key key, const Variant &value, Error *err = nullptr) {
1524 if constexpr (detail::hasVariantLookupDispatchV<T>) {
1525 String name = key.name();
1526 if (!name.isEmpty()) {
1527 return instance.variantLookupAssign(name, value, err);
1528 }
1529 return assignKeyIdDirect(instance, key.id(), value, err);
1530 } else {
1531 return assignKeyIdDirect(instance, key.id(), value, err);
1532 }
1533 }
1534
1538 static bool assign(T &instance, Key key, size_t index, const Variant &value, Error *err = nullptr) {
1539 return assignIndexedKeyIdDirect(instance, key.id(), index, value, err);
1540 }
1541
1546 static bool assignKeyIdDirect(T &instance, uint64_t id, const Variant &value, Error *err = nullptr) {
1547 if (err != nullptr) *err = Error::Ok;
1548 const Registry &r = registry();
1549 ReadWriteLock::ReadLocker lock(r.lock);
1550 auto it = r.scalars.find(id);
1551 if (it == r.scalars.end()) {
1552 if (r.inherit.assignByKey) {
1553 return r.inherit.assignByKey(instance, id, value, err);
1554 }
1555 if (err != nullptr) *err = Error::IdNotFound;
1556 return false;
1557 }
1558 if (!it->second.set) {
1559 if (err != nullptr) *err = Error::ReadOnly;
1560 return false;
1561 }
1562 Error setErr = it->second.set(instance, value);
1563 if (setErr.isError()) {
1564 if (err != nullptr) *err = setErr;
1565 return false;
1566 }
1567 return true;
1568 }
1569
1575 static bool assignIndexedKeyIdDirect(T &instance, uint64_t id, size_t index, const Variant &value,
1576 Error *err = nullptr) {
1577 if (err != nullptr) *err = Error::Ok;
1578 const Registry &r = registry();
1579 ReadWriteLock::ReadLocker lock(r.lock);
1580 auto it = r.indexedScalars.find(id);
1581 if (it == r.indexedScalars.end()) {
1582 if (r.inherit.assignIndexedByKey) {
1583 return r.inherit.assignIndexedByKey(instance, id, index, value, err);
1584 }
1585 if (err != nullptr) *err = Error::IdNotFound;
1586 return false;
1587 }
1588 if (!it->second.set) {
1589 if (err != nullptr) *err = Error::ReadOnly;
1590 return false;
1591 }
1592 Error setErr = it->second.set(instance, index, value);
1593 if (setErr.isError()) {
1594 if (err != nullptr) *err = setErr;
1595 return false;
1596 }
1597 return true;
1598 }
1599
1600 // ============================================================
1601 // Spec discovery
1602 // ============================================================
1603
1626 static const VariantSpec *specFor(const String &key, Error *err = nullptr) {
1627 if constexpr (detail::hasVariantLookupDispatchV<T>) {
1628 // specFor dispatch needs an instance to
1629 // land on the concrete type's
1630 // VariantLookup — but specFor is a
1631 // type-level query with no instance in
1632 // hand. Fall through to the static
1633 // chain rooted at T; the cascade then
1634 // climbs up through declared base
1635 // classes, which is the correct answer
1636 // for any key inherited from the base
1637 // chain. Keys registered strictly on
1638 // a sibling subclass are
1639 // type-dependent and out of scope for
1640 // an instance-less spec lookup.
1641 return specForDirect(key, err);
1642 } else {
1643 return specForDirect(key, err);
1644 }
1645 }
1646
1650 static const VariantSpec *specForDirect(const String &key, Error *err = nullptr) {
1651 if (err != nullptr) *err = Error::Ok;
1652 detail::VariantLookupSegment seg;
1653 if (!detail::parseLeadingSegment(key, seg, err)) return nullptr;
1654
1655 const Registry &r = registry();
1656 ReadWriteLock::ReadLocker lock(r.lock);
1657 const uint64_t id = r.findId(seg.name);
1658 if (id == Registry::Invalid) {
1659 if (r.inherit.specFor) {
1660 return r.inherit.specFor(key, err);
1661 }
1662 if (err != nullptr) *err = Error::IdNotFound;
1663 return nullptr;
1664 }
1665
1666 if (!seg.hasRest) {
1667 if (seg.hasIndex) {
1668 auto it = r.indexedScalars.find(id);
1669 if (it != r.indexedScalars.end()) return it->second.spec;
1670 // "Name[N]" against a variantTree binding —
1671 // the spec is the element-spec of the tree's
1672 // declared spec, surfaced by the binding's
1673 // own specFor handler.
1674 auto vtIt = r.indexedVariantTrees.find(id);
1675 if (vtIt != r.indexedVariantTrees.end()) {
1676 return vtIt->second.specFor(String(), err);
1677 }
1678 if (r.inherit.specFor) {
1679 return r.inherit.specFor(key, err);
1680 }
1681 if (err != nullptr) *err = Error::IdNotFound;
1682 return nullptr;
1683 }
1684 auto it = r.scalars.find(id);
1685 if (it != r.scalars.end()) return it->second.spec;
1686 // Terminal "Name" against a variantTree binding —
1687 // the spec describes the whole registered Variant.
1688 auto vtIt = r.variantTrees.find(id);
1689 if (vtIt != r.variantTrees.end()) {
1690 return vtIt->second.specFor(String(), err);
1691 }
1692 if (r.inherit.specFor) {
1693 return r.inherit.specFor(key, err);
1694 }
1695 if (err != nullptr) *err = Error::IdNotFound;
1696 return nullptr;
1697 }
1698
1699 if (seg.hasIndex) {
1700 auto it = r.indexedChildren.find(id);
1701 if (it != r.indexedChildren.end()) {
1702 return it->second.specFor(seg.rest, err);
1703 }
1704 auto vtIt = r.indexedVariantTrees.find(id);
1705 if (vtIt != r.indexedVariantTrees.end()) {
1706 return vtIt->second.specFor(seg.rest, err);
1707 }
1708 if (r.inherit.specFor) {
1709 return r.inherit.specFor(key, err);
1710 }
1711 if (err != nullptr) *err = Error::IdNotFound;
1712 return nullptr;
1713 }
1714
1715 auto itChild = r.children.find(id);
1716 if (itChild != r.children.end()) {
1717 return itChild->second.specFor(seg.rest, err);
1718 }
1719 auto itDb = r.databases.find(id);
1720 if (itDb != r.databases.end()) {
1721 return itDb->second.specFor(seg.rest, err);
1722 }
1723 auto itVt = r.variantTrees.find(id);
1724 if (itVt != r.variantTrees.end()) {
1725 return itVt->second.specFor(seg.rest, err);
1726 }
1727 if (r.inherit.specFor) {
1728 return r.inherit.specFor(key, err);
1729 }
1730 if (err != nullptr) *err = Error::IdNotFound;
1731 return nullptr;
1732 }
1733
1743 static const VariantSpec *specFor(Key key, Error *err = nullptr) {
1744 return specForKeyIdDirect(key.id(), err);
1745 }
1746
1751 static const VariantSpec *specForKeyIdDirect(uint64_t id, Error *err = nullptr) {
1752 if (err != nullptr) *err = Error::Ok;
1753 const Registry &r = registry();
1754 ReadWriteLock::ReadLocker lock(r.lock);
1755 auto it = r.scalars.find(id);
1756 if (it != r.scalars.end()) return it->second.spec;
1757 auto itIdx = r.indexedScalars.find(id);
1758 if (itIdx != r.indexedScalars.end()) return itIdx->second.spec;
1759 if (r.inherit.specForByKey) {
1760 return r.inherit.specForByKey(id, err);
1761 }
1762 if (err != nullptr) *err = Error::IdNotFound;
1763 return nullptr;
1764 }
1765
1766 // ============================================================
1767 // Introspection
1768 // ============================================================
1769
1777 static StringList registeredScalars() {
1778 return mergeWithInherited(collectNames(registry().scalars),
1779 registry().inherit.registeredScalars);
1780 }
1781
1783 static StringList registeredIndexedScalars() {
1784 return mergeWithInherited(collectNames(registry().indexedScalars),
1785 registry().inherit.registeredIndexedScalars);
1786 }
1787
1789 static StringList registeredChildren() {
1790 return mergeWithInherited(collectNames(registry().children),
1791 registry().inherit.registeredChildren);
1792 }
1793
1795 static StringList registeredIndexedChildren() {
1796 return mergeWithInherited(collectNames(registry().indexedChildren),
1797 registry().inherit.registeredIndexedChildren);
1798 }
1799
1801 static StringList registeredDatabases() {
1802 return mergeWithInherited(collectNames(registry().databases),
1803 registry().inherit.registeredDatabases);
1804 }
1805
1823 template <typename Fn> static void forEachScalar(Fn &&fn) {
1824 const Registry &r = registry();
1825 StringList names;
1826 Function<void(const Function<void(const String &)> &)> inheritForEach;
1827 {
1828 ReadWriteLock::ReadLocker lock(r.lock);
1829 for (auto it = r.scalars.cbegin(); it != r.scalars.cend(); ++it) {
1830 auto n = r.names.find(it->first);
1831 if (n != r.names.end()) names.pushToBack(n->second);
1832 }
1833 inheritForEach = r.inherit.forEachScalar;
1834 }
1835 // Pull inherited names in through the fallback —
1836 // this recurses transparently because
1837 // @c VariantLookup<Base>::forEachScalar also
1838 // consults its own inherit chain.
1839 if (inheritForEach) {
1840 inheritForEach([&names](const String &n) { names.pushToBack(n); });
1841 }
1842 names = names.sort();
1843 // De-duplicate — a derived class may shadow a
1844 // base's scalar with the same name; emit only
1845 // once so dumps don't print the key twice.
1846 String prev;
1847 for (const String &name : names) {
1848 if (!prev.isEmpty() && name == prev) continue;
1849 fn(name);
1850 prev = name;
1851 }
1852 }
1853
1854 // ============================================================
1855 // Recursive dump
1856 // ============================================================
1857
1883 static StringList dump(const T &instance, const String &indent = String()) {
1884 StringList out;
1885 // Scalars — forEachScalar handles the cascade
1886 // and de-duplication, resolve() handles the
1887 // polymorphic-dispatch to the concrete type.
1888 // Each line includes the Variant's type name
1889 // in brackets so scalar output matches the
1890 // @ref Metadata::dump / @ref VariantDatabase
1891 // dump format (@c "<Key> [<Type>]: <Value>"),
1892 // letting introspection consumers parse every
1893 // line uniformly without branching on
1894 // scalar-vs-db-entry.
1895 forEachScalar([&out, &instance, &indent](const String &name) {
1896 auto v = VariantLookup<T>::resolve(instance, name);
1897 if (v.hasValue()) {
1898 out.pushToBack(indent + name + " [" + v->typeName() +
1899 "]: " + v->format(String()));
1900 }
1901 });
1902 // Composites — children, indexed children, and
1903 // databases — cascade via dumpComposites so a
1904 // base class's Desc / Meta bindings land in
1905 // the derived dump too.
1906 StringList composites = dumpComposites(instance, indent);
1907 for (const String &l : composites) out.pushToBack(l);
1908 return out;
1909 }
1910
1920 static StringList dumpComposites(const T &instance, const String &indent = String()) {
1921 StringList out;
1922
1923 // Snapshot per-entry dump lambdas + names under
1924 // the read lock so we can drop the lock before
1925 // recursing into VariantLookup<U>::dump (which
1926 // takes its own VariantLookup<U> registry lock;
1927 // holding two locks invites hidden deadlocks as
1928 // the hierarchy grows).
1929 struct Item {
1930 String name;
1931 Function<StringList(const T &, const String &)> dump;
1932 };
1933 List<Item> children;
1934 List<Item> indexedChildren;
1935 List<Item> databases;
1936 List<Item> variantTrees;
1937 Function<StringList(const T &, const String &)> inheritFn;
1938 {
1939 const Registry &r = registry();
1940 ReadWriteLock::ReadLocker lock(r.lock);
1941 for (auto it = r.children.cbegin(); it != r.children.cend(); ++it) {
1942 if (!it->second.dump) continue;
1943 auto n = r.names.find(it->first);
1944 if (n == r.names.end()) continue;
1945 children.pushToBack(Item{n->second, it->second.dump});
1946 }
1947 for (auto it = r.indexedChildren.cbegin(); it != r.indexedChildren.cend(); ++it) {
1948 if (!it->second.dump) continue;
1949 auto n = r.names.find(it->first);
1950 if (n == r.names.end()) continue;
1951 indexedChildren.pushToBack(Item{n->second, it->second.dump});
1952 }
1953 for (auto it = r.databases.cbegin(); it != r.databases.cend(); ++it) {
1954 if (!it->second.dump) continue;
1955 auto n = r.names.find(it->first);
1956 if (n == r.names.end()) continue;
1957 databases.pushToBack(Item{n->second, it->second.dump});
1958 }
1959 // variantTree bindings get included with the
1960 // same dump shape as databases — one section
1961 // per registered name, with the bound Variant
1962 // rendered via Variant::format(). Indexed
1963 // variant-trees deliberately register an empty
1964 // dump so we don't double-emit the same tree
1965 // under two names.
1966 for (auto it = r.variantTrees.cbegin(); it != r.variantTrees.cend(); ++it) {
1967 if (!it->second.dump) continue;
1968 auto n = r.names.find(it->first);
1969 if (n == r.names.end()) continue;
1970 variantTrees.pushToBack(Item{n->second, it->second.dump});
1971 }
1972 inheritFn = r.inherit.dumpComposites;
1973 }
1974
1975 const String sub = indent + " ";
1976
1977 auto emit = [&out, &indent, &sub](const String &name, const StringList &lines) {
1978 if (lines.isEmpty()) return;
1979 out.pushToBack(indent + name + ":");
1980 for (const String &l : lines) out.pushToBack(l);
1981 };
1982
1983 for (const Item &c : children) {
1984 emit(c.name, c.dump(instance, sub));
1985 }
1986 for (const Item &c : indexedChildren) {
1987 emit(c.name, c.dump(instance, sub));
1988 }
1989 for (const Item &d : databases) {
1990 emit(d.name, d.dump(instance, sub));
1991 }
1992 for (const Item &d : variantTrees) {
1993 emit(d.name, d.dump(instance, sub));
1994 }
1995
1996 // Pick up composites declared on the inherited
1997 // base so a derived dump is truly transitive.
1998 if (inheritFn) {
1999 StringList inherited = inheritFn(instance, indent);
2000 for (const String &l : inherited) out.pushToBack(l);
2001 }
2002 return out;
2003 }
2004
2005 // ============================================================
2006 // Template-string formatting
2007 // ============================================================
2008
2039 template <typename Resolver>
2040 static String format(const T &instance, const String &tmpl, Resolver &&resolver, Error *err = nullptr) {
2041 if (err != nullptr) *err = Error::Ok;
2042 String out;
2043 out.reserve(tmpl.byteCount());
2044 const char *src = tmpl.cstr();
2045 const size_t len = tmpl.byteCount();
2046 bool sawUnresolved = false;
2047 size_t i = 0;
2048 while (i < len) {
2049 char c = src[i];
2050 if (c == '{') {
2051 if (i + 1 < len && src[i + 1] == '{') {
2052 out.pushBack('{');
2053 i += 2;
2054 continue;
2055 }
2056 size_t end = i + 1;
2057 while (end < len && src[end] != '}') ++end;
2058 if (end >= len) {
2059 out += String(src + i, len - i);
2060 break;
2061 }
2062 const char *bodyData = src + i + 1;
2063 const size_t bodyLen = end - (i + 1);
2064 size_t colon = bodyLen;
2065 for (size_t p = 0; p < bodyLen; ++p) {
2066 if (bodyData[p] == ':') {
2067 colon = p;
2068 break;
2069 }
2070 }
2071 const size_t keyLen = colon;
2072 const size_t specOff = (colon == bodyLen) ? bodyLen : colon + 1;
2073 String keyName(bodyData, keyLen);
2074 String specStr(bodyData + specOff, bodyLen - specOff);
2075 auto v = resolve(instance, keyName);
2076 if (v.hasValue()) {
2077 out += v->format(specStr);
2078 } else {
2079 Optional<String> resolved;
2080 if constexpr (!std::is_same_v<std::decay_t<Resolver>, std::nullptr_t>) {
2081 resolved = resolver(keyName, specStr);
2082 }
2083 if (resolved.hasValue()) {
2084 out += *resolved;
2085 } else {
2086 sawUnresolved = true;
2087 out += '?';
2088 out += String(bodyData, keyLen);
2089 out += '?';
2090 }
2091 }
2092 i = end + 1;
2093 } else if (c == '}') {
2094 if (i + 1 < len && src[i + 1] == '}') {
2095 out.pushBack('}');
2096 i += 2;
2097 } else {
2098 out.pushBack('}');
2099 ++i;
2100 }
2101 } else {
2102 out.pushBack(c);
2103 ++i;
2104 }
2105 }
2106 if (sawUnresolved && err != nullptr) *err = Error::IdNotFound;
2107 return out;
2108 }
2109
2117 static String format(const T &instance, const String &tmpl, Error *err = nullptr) {
2118 return format(instance, tmpl, nullptr, err);
2119 }
2120
2121 private:
2122 struct ScalarEntry {
2123 ScalarGet get;
2124 ScalarSet set;
2125 const VariantSpec *spec = nullptr;
2126 };
2127 struct IndexedScalarEntry {
2128 IndexedScalarGet get;
2129 IndexedScalarSet set;
2130 const VariantSpec *spec = nullptr;
2131 };
2132 struct ComposeEntry {
2133 ComposeResolve resolve;
2134 ComposeAssign assign;
2135 ComposeSpecFor specFor;
2149 Function<StringList(const T &, const String &)> dump;
2150 };
2151 struct IndexedCompEntry {
2152 IndexedCompResolve resolve;
2153 IndexedCompAssign assign;
2154 ComposeSpecFor specFor;
2166 Function<StringList(const T &, const String &)> dump;
2167 };
2168
2179 struct InheritEntry {
2180 Function<Optional<Variant>(const T &, const String &, Error *)> resolve;
2181 Function<bool(T &, const String &, const Variant &, Error *)> assign;
2182 Function<const VariantSpec *(const String &, Error *)> specFor;
2183 Function<Optional<Variant>(const T &, uint64_t, Error *)> resolveByKey;
2184 Function<bool(T &, uint64_t, const Variant &, Error *)> assignByKey;
2185 Function<const VariantSpec *(uint64_t, Error *)> specForByKey;
2186 Function<Optional<Variant>(const T &, uint64_t, size_t, Error *)>
2187 resolveIndexedByKey;
2188 Function<bool(T &, uint64_t, size_t, const Variant &, Error *)> assignIndexedByKey;
2189 Function<void(const Function<void(const String &)> &)> forEachScalar;
2190 Function<StringList()> registeredScalars;
2191 Function<StringList()> registeredIndexedScalars;
2192 Function<StringList()> registeredChildren;
2193 Function<StringList()> registeredIndexedChildren;
2194 Function<StringList()> registeredDatabases;
2205 Function<StringList(const T &, const String &)> dumpComposites;
2206 };
2207
2220 struct Registry {
2221 static constexpr uint64_t Invalid = UINT64_MAX;
2222
2223 Map<uint64_t, String> names;
2224 Map<uint64_t, ScalarEntry> scalars;
2225 Map<uint64_t, IndexedScalarEntry> indexedScalars;
2226 Map<uint64_t, ComposeEntry> children;
2227 Map<uint64_t, IndexedCompEntry> indexedChildren;
2228 Map<uint64_t, ComposeEntry> databases;
2229 Map<uint64_t, ComposeEntry> variantTrees;
2230 Map<uint64_t, IndexedCompEntry> indexedVariantTrees;
2231 InheritEntry inherit;
2232 mutable ReadWriteLock lock;
2233
2245 uint64_t declareName(const String &name) {
2246 const uint64_t h = fnv1a(name.cstr());
2247 auto it = names.find(h);
2248 if (it != names.end()) {
2249 if (it->second == name) return h;
2250 detail::stringRegistryCollisionAbort(typeid(T).name(), it->second, name,
2251 h);
2252 }
2253 names.insert(h, name);
2254 return h;
2255 }
2256
2265 uint64_t findId(const String &name) const {
2266 const uint64_t h = fnv1a(name.cstr());
2267 auto it = names.find(h);
2268 if (it == names.end()) return Invalid;
2269 if (it->second != name) return Invalid;
2270 return h;
2271 }
2272 };
2273
2274 static Registry &registry() {
2275 static Registry r;
2276 return r;
2277 }
2278
2288 template <typename MapT> static StringList collectNames(const MapT &m) {
2289 const Registry &r = registry();
2290 ReadWriteLock::ReadLocker lock(r.lock);
2291 StringList out;
2292 for (auto it = m.cbegin(); it != m.cend(); ++it) {
2293 auto n = r.names.find(it->first);
2294 if (n != r.names.end()) out.pushToBack(n->second);
2295 }
2296 out = out.sort();
2297 return out;
2298 }
2299
2308 static StringList mergeWithInherited(StringList ownNames,
2309 const Function<StringList()> &inheritFn) {
2310 if (!inheritFn) return ownNames;
2311 StringList inherited = inheritFn();
2312 for (const String &n : inherited) ownNames.pushToBack(n);
2313 ownNames = ownNames.sort();
2314 StringList out;
2315 String prev;
2316 for (const String &n : ownNames) {
2317 if (!prev.isEmpty() && n == prev) continue;
2318 out.pushToBack(n);
2319 prev = n;
2320 }
2321 return out;
2322 }
2323};
2324
2325PROMEKI_NAMESPACE_END
2326
2353#define PROMEKI_LOOKUP_REGISTER(Type) \
2354 [[maybe_unused]] static inline const ::promeki::VariantLookup<Type>::Registrar PROMEKI_CONCAT( \
2355 _promeki_lookup_reg_, __COUNTER__) = ::promeki::VariantLookup<Type>::registrar()
2356
2357#endif // PROMEKI_ENABLE_CORE