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 <cstddef>
4#include <meta>
5#include <string>
6#include <vector>
7
8#include <welder/containers.hpp> // is_reference_container / container_kind_of
9#include <welder/naming.hpp> // restyle / case_kind (name derivation)
10#include <welder/reflect.hpp> // welded_for (element eligibility)
11
27
28namespace welder::inline v0::rods::opaque_containers {
29
30// --- type spelling & name derivation (consteval) ----------------------------
31
36consteval std::string container_spelling(std::meta::info type) {
37 return std::string{std::meta::display_string_of(std::meta::dealias(type))};
38}
39
42consteval std::size_t value_arg_count(std::meta::info type) {
43 return ::welder::container_kind_of(type) == ::welder::container_kind::map ? 2u : 1u;
44}
45
50consteval std::string sanitize_ident(std::string s) {
51 std::string out{};
52 bool pending_sep{false};
53 for (char c : s) {
54 const bool ok{(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
55 (c >= '0' && c <= '9') || c == '_'};
56 if (ok) {
57 if (pending_sep && !out.empty())
58 out += '_';
59 out += c;
60 pending_sep = false;
61 } else {
62 pending_sep = true; // collapse a run of non-identifier chars into one '_'
63 }
64 }
65 if (out.empty())
66 out = "T";
67 if (out[0] >= '0' && out[0] <= '9')
68 out.insert(out.begin(), '_');
69 return out;
70}
71
86consteval std::string qualified_ident(std::meta::info entity) {
87 auto keep = [](std::meta::info p) {
88 if (!std::meta::has_identifier(p))
89 return false; // anonymous namespace: nothing to spell
90 const std::string id{std::meta::identifier_of(p)};
91 if (id == "std")
92 return false; // std::vector<int> should read VectorInt, not StdVectorInt
93 return !(id.size() >= 2 && id[0] == '_' && id[1] == '_'); // __cxx11, __1, …
94 };
95 std::vector<std::string> parts{};
96 parts.push_back(::welder::naming::restyle(
97 std::meta::has_identifier(entity)
98 ? std::string{std::meta::identifier_of(entity)}
99 : std::string{std::meta::display_string_of(entity)},
101 // Walk enclosing scopes. A fundamental type (int, double) has no parent —
102 // `parent_of` THROWS there, so gate every step on `has_parent`.
103 for (std::meta::info e{entity};
104 std::meta::has_parent(e) && std::meta::parent_of(e) != ^^::;) {
105 e = std::meta::parent_of(e);
106 if (!(std::meta::is_namespace(e) ||
107 (std::meta::is_type(e) && std::meta::is_class_type(e))))
108 break; // a function/block scope etc. — stop qualifying
109 if (keep(e))
110 parts.push_back(::welder::naming::restyle(
111 std::string{std::meta::identifier_of(e)},
113 }
114 std::string out{};
115 for (auto it{parts.rbegin()}; it != parts.rend(); ++it)
116 out += *it;
117 return out;
118}
119
134consteval std::string derive_name(std::meta::info arg) {
135 // A non-type (NTTP) template argument is a value, not a type — render its display
136 // (e.g. `ClientVersion{3,3,5,12340}`); the caller's final sanitize legalizes it.
137 if (!std::meta::is_type(arg))
138 return std::string{std::meta::display_string_of(arg)};
139 const std::meta::info type{std::meta::dealias(arg)};
140 // std::string (and wstring, …) is basic_string<Char, …> — fold to a clean word.
141 if (std::meta::has_template_arguments(type) &&
142 std::meta::template_of(type) == ^^std::basic_string)
143 return "String";
145 std::string out{qualified_ident(std::meta::template_of(type))};
146 const auto args{std::meta::template_arguments_of(type)};
147 for (std::size_t i{0}, n{value_arg_count(type)}; i < n; ++i)
148 out += derive_name(args[i]);
149 return out;
150 }
151 // Any OTHER class-template specialization: recurse structurally over ALL its
152 // arguments (template name + each arg), so an NTTP / nested specialization reduces
153 // to identifier tokens rather than a raw template-id spelling.
154 if (std::meta::has_template_arguments(type)) {
155 std::string out{qualified_ident(std::meta::template_of(type))};
156 for (const std::meta::info a : std::meta::template_arguments_of(type))
157 out += derive_name(a);
158 return out;
159 }
160 // A plain type: its qualified identifier (namespaces + enclosing classes), or its
161 // display for an unnameable one (the caller's final sanitize legalizes either).
162 return qualified_ident(type);
163}
164
170consteval bool has_opaque_hook(std::meta::info style_type) {
171 const std::meta::access_context ctx{std::meta::access_context::unprivileged()};
172 for (const std::meta::info m : std::meta::members_of(style_type, ctx))
173 if (std::meta::is_function(m) && std::meta::has_identifier(m) &&
174 std::meta::identifier_of(m) == "transform_opaque_container")
175 return true;
176 for (const std::meta::info b : std::meta::bases_of(style_type, ctx))
177 if (has_opaque_hook(std::meta::type_of(b)))
178 return true;
179 return false;
180}
181
198template <class Style, std::meta::info Enclosing, std::meta::info Container,
199 std::meta::info Member>
200consteval std::string opaque_name() {
201 if constexpr (has_opaque_hook(^^Style))
202 return sanitize_ident(std::string{
203 Style::transform_opaque_container(Enclosing, Container, Member)});
204 else
205 return sanitize_ident(derive_name(Container));
206}
207
210consteval std::string namespace_name(std::meta::info ns) {
211 std::string tail{std::meta::identifier_of(ns)};
212 for (std::meta::info p{std::meta::parent_of(ns)};
213 p != std::meta::info{} && p != ^^:: && std::meta::has_identifier(p);
214 p = std::meta::parent_of(p))
215 tail = std::string{std::meta::identifier_of(p)} + "::" + tail;
216 return tail;
217}
218
219// --- collecting the reference containers within a surface type --------------
220
223consteval bool scalar_leaf(std::meta::info t) {
224 t = std::meta::dealias(t);
225 if (std::meta::is_fundamental_type(t))
226 return true;
227 return std::meta::has_template_arguments(t) &&
228 std::meta::template_of(t) == ^^std::basic_string;
229}
230
241consteval bool element_ok(std::meta::info t) {
242 t = std::meta::dealias(t);
243 if (scalar_leaf(t))
244 return true;
245 if (is_reference_container(t)) // nested container — inner is not a predeclared name
246 return false;
247 if (std::meta::is_class_type(t) || std::meta::is_enum_type(t))
248 // top-level (namespace-scoped) welded type => predeclared in phase 1
249 // (is_namespace, not !is_class_type: is_class_type THROWS on a namespace)
250 return std::meta::is_namespace(std::meta::parent_of(t)) &&
252 return false;
253}
254
260consteval bool opaque_eligible(std::meta::info type) {
261 const auto args{std::meta::template_arguments_of(type)};
262 for (std::size_t i{0}, n{value_arg_count(type)}; i < n; ++i)
263 if (!element_ok(args[i]))
264 return false;
265 return true;
266}
267
273consteval void collect_into(std::vector<std::meta::info>& out, std::meta::info type) {
274 const std::meta::info u{std::meta::dealias(std::meta::remove_cvref(type))};
276 out.push_back(u);
277}
278
281template <std::meta::info Type>
282consteval std::vector<std::meta::info> containers_in() {
283 std::vector<std::meta::info> out{};
284 collect_into(out, Type);
285 return out;
286}
287
288// --- the growing generated header -------------------------------------------
289
293struct entry {
294 std::string spelling;
295 std::string name;
296 bool excluded;
297 bool styled;
304};
305
309struct document {
310 std::vector<entry> entries{};
311
319 template <std::meta::info C, class Style, std::meta::info Enclosing,
320 std::meta::info Site>
321 void add_one(bool excluded, bool styled_site) {
322 const char* sp{std::define_static_string(container_spelling(C))};
323 const char* nm{std::define_static_string(opaque_name<Style, Enclosing, C, Site>())};
324 for (entry& e : entries)
325 if (e.spelling == sp) {
326 e.excluded = e.excluded || excluded;
327 if (styled_site && !e.styled) { // a Style-aware name beats a default one
328 e.name = nm;
329 e.styled = true;
330 }
331 return;
332 }
333 entries.push_back({std::string{sp}, std::string{nm}, excluded, styled_site});
334 }
335
340 template <std::meta::info SurfaceType, class Style, std::meta::info Enclosing,
341 std::meta::info Site>
342 void collect([[maybe_unused]] bool excluded, [[maybe_unused]] bool styled_site) {
343 // Unused when SurfaceType names no eligible container (the common case — the
344 // template for below is then empty); [[maybe_unused]] keeps -Wunused quiet.
345 template for (constexpr auto c :
346 std::define_static_array(containers_in<SurfaceType>()))
347 add_one<c, Style, Enclosing, Site>(excluded, styled_site);
348 }
349
355 template <std::meta::info Fn, class Style>
356 void collect_callable(bool styled_site) {
357 template for (constexpr auto p :
358 std::define_static_array(std::meta::parameters_of(Fn)))
360 false, styled_site);
361 if constexpr (!std::meta::is_constructor(Fn)) {
362 using R = [:std::meta::return_type_of(Fn):];
363 if constexpr (!std::is_void_v<R>)
364 collect<std::meta::return_type_of(Fn), Style, std::meta::parent_of(Fn),
365 Fn>(false, styled_site);
366 }
367 }
368
373 std::string render(const std::string& ns) const {
374 std::vector<entry> es{entries};
375 std::sort(es.begin(), es.end(),
376 [](const entry& a, const entry& b) { return a.name < b.name; });
377 for (std::size_t i{1}; i < es.size(); ++i)
378 if (!es[i].excluded && !es[i - 1].excluded &&
379 es[i].name == es[i - 1].name &&
380 es[i].spelling != es[i - 1].spelling)
381 return "#error welder: two distinct opaque containers derive the "
382 "same name '" +
383 es[i].name + "' (" + es[i - 1].spelling + " vs " +
384 es[i].spelling +
385 "); this generator cannot rename them automatically.\n";
386 std::string out{};
387 out += "#pragma once\n";
388 out += "// AUTO-GENERATED by welder (welder::rods::opaque_containers). Do not edit.\n";
389 out += "//\n";
390 out += "// Include AFTER your welded type headers and the active backend's\n";
391 out += "// <welder/rods/python/{pybind11,nanobind}/rod.hpp>, before the module.\n";
392 out += "#include <map>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n";
393 for (const entry& e : es)
394 if (!e.excluded)
395 out += "WELDER_OPAQUE(" + e.spelling + ")\n";
396 out += "\nnamespace " + ns + " {\n";
397 for (const entry& e : es)
398 if (!e.excluded)
399 out += "using " + e.name +
400 " [[=welder::weld(welder::lang::py)]] = " + e.spelling + ";\n";
401 out += "} // namespace " + ns + "\n";
402 return out;
403 }
404};
405
406} // 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:86
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:170
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:210
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:134
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:273
consteval std::string opaque_name()
The opaque-wrapper name for container Container found on member/site Member within Enclosing — the cu...
Definition document.hpp:200
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:223
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:36
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:42
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:282
consteval bool opaque_eligible(std::meta::info type)
Is container type eligible for the generator — will PHASE 1 register every element/key/value type's n...
Definition document.hpp:260
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:50
consteval bool element_ok(std::meta::info t)
Can the driver's PHASE 1 (name pre-registration) register t's name before PHASE 2 binds the container...
Definition document.hpp:241
consteval bool welded_for(std::meta::info type, lang L)
Is type welded for language L — i.e.
Definition reflect.hpp:27
@ 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:309
void collect(bool excluded, bool styled_site)
Collect every reference container within surface type SurfaceType (a data member / parameter / return...
Definition document.hpp:342
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:356
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:373
std::vector< entry > entries
Deduped by C++ spelling.
Definition document.hpp:310
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:321
One container to open opaque: its C++ spelling, its derived target name, and whether a by_value mark ...
Definition document.hpp:293
bool styled
name came from a Style-aware site (a data member / method / free function / variable),...
Definition document.hpp:297
bool excluded
a by_value mark opted this container type out.
Definition document.hpp:296