welder 0.1.0
Bindings for annotated C++ types, from C++26 reflection
Loading...
Searching...
No Matches
document.hpp
Go to the documentation of this file.
1#pragma once
2#include <algorithm>
3#include <array>
4#include <cstddef>
5#include <meta>
6#include <string>
7#include <vector>
8
9#include <welder/containers.hpp> // is_reference_container / container_kind_of
10#include <welder/naming.hpp> // restyle / case_kind (name derivation)
11#include <welder/reflect.hpp> // welded_for (element eligibility)
12
28
29namespace welder::inline v0::rods::opaque_containers {
30
31// --- type spelling & name derivation (consteval) ----------------------------
32
37consteval std::string container_spelling(std::meta::info type) {
38 return std::string{std::meta::display_string_of(std::meta::dealias(type))};
39}
40
43consteval std::size_t value_arg_count(std::meta::info type) {
44 return ::welder::container_kind_of(type) == ::welder::container_kind::map ? 2u : 1u;
45}
46
51consteval std::string sanitize_ident(std::string s) {
52 std::string out{};
53 bool pending_sep{false};
54 for (char c : s) {
55 const bool ok{(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
56 (c >= '0' && c <= '9') || c == '_'};
57 if (ok) {
58 if (pending_sep && !out.empty())
59 out += '_';
60 out += c;
61 pending_sep = false;
62 } else {
63 pending_sep = true; // collapse a run of non-identifier chars into one '_'
64 }
65 }
66 if (out.empty())
67 out = "T";
68 if (out[0] >= '0' && out[0] <= '9')
69 out.insert(out.begin(), '_');
70 return out;
71}
72
87consteval std::string qualified_ident(std::meta::info entity) {
88 auto keep = [](std::meta::info p) {
89 if (!std::meta::has_identifier(p))
90 return false; // anonymous namespace: nothing to spell
91 const std::string id{std::meta::identifier_of(p)};
92 if (id == "std")
93 return false; // std::vector<int> should read VectorInt, not StdVectorInt
94 return !(id.size() >= 2 && id[0] == '_' && id[1] == '_'); // __cxx11, __1, …
95 };
96 std::vector<std::string> parts{};
97 parts.push_back(::welder::naming::restyle(
98 std::meta::has_identifier(entity)
99 ? std::string{std::meta::identifier_of(entity)}
100 : std::string{std::meta::display_string_of(entity)},
102 // Walk enclosing scopes. A fundamental type (int, double) has no parent —
103 // `parent_of` THROWS there, so gate every step on `has_parent`.
104 for (std::meta::info e{entity};
105 std::meta::has_parent(e) && std::meta::parent_of(e) != ^^::;) {
106 e = std::meta::parent_of(e);
107 if (!(std::meta::is_namespace(e) ||
108 (std::meta::is_type(e) && std::meta::is_class_type(e))))
109 break; // a function/block scope etc. — stop qualifying
110 if (keep(e))
111 parts.push_back(::welder::naming::restyle(
112 std::string{std::meta::identifier_of(e)},
114 }
115 std::string out{};
116 for (auto it{parts.rbegin()}; it != parts.rend(); ++it)
117 out += *it;
118 return out;
119}
120
123consteval std::string decimal_string(std::size_t n) {
124 if (n == 0)
125 return "0";
126 std::string s{};
127 for (; n; n /= 10)
128 s.insert(s.begin(), char('0' + n % 10));
129 return s;
130}
131
153consteval std::string derive_name(std::meta::info arg) {
154 // A non-type (NTTP) template argument is a value, not a type — render its display
155 // (e.g. `Extent{2,3,4,5}`); the caller's final sanitize legalizes it.
156 if (!std::meta::is_type(arg))
157 return std::string{std::meta::display_string_of(arg)};
158 const std::meta::info type{std::meta::dealias(arg)};
159 // std::string (and wstring, …) is basic_string<Char, …> — fold to a clean word.
160 if (std::meta::has_template_arguments(type) &&
161 std::meta::template_of(type) == ^^std::basic_string)
162 return "String";
164 const auto args{std::meta::template_arguments_of(type)};
165 // A fixed-size array names its element then its extent (Array{Elem}x{N}); the
166 // extent is a non-type argument (a value), read directly, not recursed.
167 if (std::meta::template_of(type) == ^^std::array)
168 return "Array" + derive_name(args[0]) + "x" +
169 decimal_string(std::meta::extract<std::size_t>(args[1]));
170 std::string out{qualified_ident(std::meta::template_of(type))};
171 for (std::size_t i{0}, n{value_arg_count(type)}; i < n; ++i)
172 out += derive_name(args[i]);
173 return out;
174 }
175 // Any OTHER class-template specialization: recurse structurally over ALL its
176 // arguments (template name + each arg), so an NTTP / nested specialization reduces
177 // to identifier tokens rather than a raw template-id spelling.
178 if (std::meta::has_template_arguments(type)) {
179 std::string out{qualified_ident(std::meta::template_of(type))};
180 for (const std::meta::info a : std::meta::template_arguments_of(type))
181 out += derive_name(a);
182 return out;
183 }
184 // A plain type: its qualified identifier (namespaces + enclosing classes), or its
185 // display for an unnameable one (the caller's final sanitize legalizes either).
186 return qualified_ident(type);
187}
188
194consteval bool has_opaque_hook(std::meta::info style_type) {
195 const std::meta::access_context ctx{std::meta::access_context::unprivileged()};
196 for (const std::meta::info m : std::meta::members_of(style_type, ctx))
197 if (std::meta::is_function(m) && std::meta::has_identifier(m) &&
198 std::meta::identifier_of(m) == "transform_opaque_container")
199 return true;
200 for (const std::meta::info b : std::meta::bases_of(style_type, ctx))
201 if (has_opaque_hook(std::meta::type_of(b)))
202 return true;
203 return false;
204}
205
222template <class Style, std::meta::info Enclosing, std::meta::info Container,
223 std::meta::info Member>
224consteval std::string opaque_name() {
225 if constexpr (has_opaque_hook(^^Style))
226 return sanitize_ident(std::string{
227 Style::transform_opaque_container(Enclosing, Container, Member)});
228 else
229 return sanitize_ident(derive_name(Container));
230}
231
234consteval std::string namespace_name(std::meta::info ns) {
235 std::string tail{std::meta::identifier_of(ns)};
236 for (std::meta::info p{std::meta::parent_of(ns)};
237 p != std::meta::info{} && p != ^^:: && std::meta::has_identifier(p);
238 p = std::meta::parent_of(p))
239 tail = std::string{std::meta::identifier_of(p)} + "::" + tail;
240 return tail;
241}
242
243// --- collecting the reference containers within a surface type --------------
244
247consteval bool scalar_leaf(std::meta::info t) {
248 t = std::meta::dealias(t);
249 if (std::meta::is_fundamental_type(t))
250 return true;
251 return std::meta::has_template_arguments(t) &&
252 std::meta::template_of(t) == ^^std::basic_string;
253}
254
255consteval bool opaque_eligible(std::meta::info type);
256
269consteval bool element_ok(std::meta::info t) {
270 t = std::meta::dealias(t);
271 if (scalar_leaf(t))
272 return true;
273 if (is_reference_container(t)) // nested container: opened opaque alongside the outer
274 return opaque_eligible(t);
275 if (std::meta::is_class_type(t) || std::meta::is_enum_type(t))
276 // top-level (namespace-scoped) welded type => predeclared in phase 1
277 // (is_namespace, not !is_class_type: is_class_type THROWS on a namespace)
278 return std::meta::is_namespace(std::meta::parent_of(t)) &&
280 return false;
281}
282
288consteval bool opaque_eligible(std::meta::info type) {
289 const auto args{std::meta::template_arguments_of(type)};
290 for (std::size_t i{0}, n{value_arg_count(type)}; i < n; ++i)
291 if (!element_ok(args[i]))
292 return false;
293 return true;
294}
295
300consteval std::size_t container_depth(std::meta::info type) {
301 const std::meta::info u{std::meta::dealias(type)};
303 return 0;
304 std::size_t inner{0};
305 const auto args{std::meta::template_arguments_of(u)};
306 for (std::size_t i{0}, n{value_arg_count(u)}; i < n; ++i)
307 inner = std::max(inner, container_depth(args[i]));
308 return 1 + inner;
309}
310
318consteval void collect_into(std::vector<std::meta::info>& out, std::meta::info type) {
319 const std::meta::info u{std::meta::dealias(std::meta::remove_cvref(type))};
321 out.push_back(u);
322 const auto args{std::meta::template_arguments_of(u)};
323 for (std::size_t i{0}, n{value_arg_count(u)}; i < n; ++i)
324 collect_into(out, args[i]);
325 }
326}
327
330template <std::meta::info Type>
331consteval std::vector<std::meta::info> containers_in() {
332 std::vector<std::meta::info> out{};
333 collect_into(out, Type);
334 return out;
335}
336
337// --- the growing generated header -------------------------------------------
338
342struct entry {
343 std::string spelling;
344 std::string name;
345 bool excluded;
346 bool styled;
353 std::size_t depth;
357};
358
362struct document {
363 std::vector<entry> entries{};
364
372 template <std::meta::info C, class Style, std::meta::info Enclosing,
373 std::meta::info Site>
374 void add_one(bool excluded, bool styled_site) {
375 const char* sp{std::define_static_string(container_spelling(C))};
376 const char* nm{std::define_static_string(opaque_name<Style, Enclosing, C, Site>())};
377 constexpr std::size_t depth{container_depth(C)};
378 for (entry& e : entries)
379 if (e.spelling == sp) {
380 e.excluded = e.excluded || excluded;
381 if (styled_site && !e.styled) { // a Style-aware name beats a default one
382 e.name = nm;
383 e.styled = true;
384 }
385 return;
386 }
387 entries.push_back({std::string{sp}, std::string{nm}, excluded, styled_site, depth});
388 }
389
394 template <std::meta::info SurfaceType, class Style, std::meta::info Enclosing,
395 std::meta::info Site>
396 void collect([[maybe_unused]] bool excluded, [[maybe_unused]] bool styled_site) {
397 // Unused when SurfaceType names no eligible container (the common case — the
398 // template for below is then empty); [[maybe_unused]] keeps -Wunused quiet.
399 template for (constexpr auto c :
400 std::define_static_array(containers_in<SurfaceType>()))
401 add_one<c, Style, Enclosing, Site>(excluded, styled_site);
402 }
403
409 template <std::meta::info Fn, class Style>
410 void collect_callable(bool styled_site) {
411 template for (constexpr auto p :
412 std::define_static_array(std::meta::parameters_of(Fn)))
414 false, styled_site);
415 if constexpr (!std::meta::is_constructor(Fn)) {
416 using R = [:std::meta::return_type_of(Fn):];
417 if constexpr (!std::is_void_v<R>)
418 collect<std::meta::return_type_of(Fn), Style, std::meta::parent_of(Fn),
419 Fn>(false, styled_site);
420 }
421 }
422
429 std::string render(const std::string& ns) const {
430 std::vector<entry> es{entries};
431 std::sort(es.begin(), es.end(), [](const entry& a, const entry& b) {
432 return a.name < b.name; // name-major first: the collision scan below
433 });
434 for (std::size_t i{1}; i < es.size(); ++i)
435 if (!es[i].excluded && !es[i - 1].excluded &&
436 es[i].name == es[i - 1].name &&
437 es[i].spelling != es[i - 1].spelling)
438 return "#error welder: two distinct opaque containers derive the "
439 "same name '" +
440 es[i].name + "' (" + es[i - 1].spelling + " vs " +
441 es[i].spelling +
442 "); this generator cannot rename them automatically.\n";
443 std::sort(es.begin(), es.end(), [](const entry& a, const entry& b) {
444 return a.depth != b.depth ? a.depth < b.depth : a.name < b.name;
445 });
446 std::string out{};
447 out += "#pragma once\n";
448 out += "// AUTO-GENERATED by welder (welder::rods::opaque_containers). Do not edit.\n";
449 out += "//\n";
450 out += "// Include AFTER your welded type headers and the active backend's\n";
451 out += "// <welder/rods/python/{pybind11,nanobind}/rod.hpp>, before the module.\n";
452 out += "#include <array>\n#include <map>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n";
453 for (const entry& e : es)
454 if (!e.excluded)
455 out += "WELDER_OPAQUE(" + e.spelling + ")\n";
456 out += "\nnamespace " + ns + " {\n";
457 for (const entry& e : es)
458 if (!e.excluded)
459 out += "using " + e.name +
460 " [[=welder::weld(welder::lang::py)]] = " + e.spelling + ";\n";
461 out += "} // namespace " + ns + "\n";
462 return out;
463 }
464};
465
466} // namespace welder::inline v0::rods::opaque_containers
The reference-semantic container table: which STL containers welder can bind opaquely* (by reference)...
consteval std::string restyle(std::string_view id, case_kind kind)
Re-spell identifier id in convention kind, preserving any leading and trailing underscore run (_priva...
Definition naming.hpp:157
consteval std::string qualified_ident(std::meta::info entity)
The collision-free qualified identifier of entity: its own identifier (or, for an unnameable entity,...
Definition document.hpp:87
consteval bool has_opaque_hook(std::meta::info style_type)
Does name style style_type (or any of its bases) declare the optional transform_opaque_container hook...
Definition document.hpp:194
consteval std::string namespace_name(std::meta::info ns)
The fully-qualified spelling of namespace ns for a namespace X { … } header block (no leading :: — ge...
Definition document.hpp:234
consteval std::string derive_name(std::meta::info arg)
A readable, valid, collision-free PascalCase identifier for container/element arg — the template name...
Definition document.hpp:153
consteval void collect_into(std::vector< std::meta::info > &out, std::meta::info type)
Append type's directly-named reference container when it is one and eligible (see opaque_eligible),...
Definition document.hpp:318
consteval std::string opaque_name()
The opaque-wrapper name for container Container found on member/site Member within Enclosing — the cu...
Definition document.hpp:224
consteval std::size_t container_depth(std::meta::info type)
How deeply container type nests further reference containers: 1 for vector<int>, 2 for vector<vector<...
Definition document.hpp:300
consteval bool scalar_leaf(std::meta::info t)
Is t a scalar leaf — a fundamental type or a std::basic_string — i.e.
Definition document.hpp:247
consteval std::string decimal_string(std::size_t n)
Decimal render of n (constexpr std::to_string is unavailable on gcc-16) — the extent token an std::ar...
Definition document.hpp:123
consteval std::string container_spelling(std::meta::info type)
The C++ text of container type, infrastructure args (allocator/comparator/ hasher) dropped — the refl...
Definition document.hpp:37
consteval std::size_t value_arg_count(std::meta::info type)
How many leading (value) template arguments of type name its element/key/value types — a sequence exp...
Definition document.hpp:43
consteval std::vector< std::meta::info > containers_in()
The reference containers within surface type Type (a splice-ready static list for a template for).
Definition document.hpp:331
consteval bool opaque_eligible(std::meta::info type)
Is container type eligible for the generator — will every element/key/value type's name exist when it...
Definition document.hpp:288
consteval std::string sanitize_ident(std::string s)
Reduce s to a valid C++ identifier: keep [A-Za-z0-9_], collapse every other run to a single _ (droppi...
Definition document.hpp:51
consteval bool element_ok(std::meta::info t)
Can element/key/value type t appear inside an opaque container — i.e.
Definition document.hpp:269
consteval bool welded_for(std::meta::info type, lang L)
Is type welded for language L — i.e.
Definition reflect.hpp:40
@ py
Python (via the pybind11 and nanobind backends).
Definition lang.hpp:43
@ map
A bind_map container: std::map, std::unordered_map.
consteval bool is_reference_container(std::meta::info type)
Is type one of the containers welder can bind by reference (opaquely)?
Language-agnostic name styling: reshape a C++ identifier into a target language's naming convention,...
Language-agnostic resolution: given a reflected type/member and a target language,...
The accumulator threaded through the rod's emission hooks: the deduped set of reference containers th...
Definition document.hpp:362
void collect(bool excluded, bool styled_site)
Collect every reference container within surface type SurfaceType (a data member / parameter / return...
Definition document.hpp:396
void collect_callable(bool styled_site)
Collect from callable Fn's parameter types and (unless a constructor) its return type — never by_valu...
Definition document.hpp:410
std::string render(const std::string &ns) const
The finished, self-contained header text — WELDER_OPAQUE(...) at global scope, the welded aliases ins...
Definition document.hpp:429
std::vector< entry > entries
Deduped by C++ spelling.
Definition document.hpp:363
void add_one(bool excluded, bool styled_site)
Record container C found on member/site Site within Enclosing (deduped by spelling; excluded OR-merge...
Definition document.hpp:374
One container to open opaque: its C++ spelling, its derived target name, and whether a by_value mark ...
Definition document.hpp:342
bool styled
name came from a Style-aware site (a data member / method / free function / variable),...
Definition document.hpp:346
bool excluded
a by_value mark opted this container type out.
Definition document.hpp:345
std::size_t depth
container nesting depth (see container_depth); shallower aliases render (and therefore weld) first,...
Definition document.hpp:353