zerodds_lang_rules/emit/cpp.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The C++ backend of [`CodecTarget`].
4//!
5//! The third spelling of the same walk, and the closest to C's of the
6//! three — which makes the places it still differs the interesting ones:
7//!
8//! - **References, not pointers.** A codec takes `const T &v`, so
9//! [`CodecTarget::member`] is `v.x` and [`CodecTarget::handoff`] hands
10//! the value. C's `&v->x` has no counterpart here and neither does its
11//! array decay: a `T[2][3]` is a `std::array<std::array<T, 3>, 2>`,
12//! which is a value.
13//! - **Methods, not free functions.** `runtime/cpp` names them exactly
14//! as the walk asks — `write_u32`, `finish_dheader` — so unlike Java
15//! nothing is re-cased. A codec for a type is a `static` free function
16//! in the probe's translation unit, which C++ does have.
17//! - **A `std::vector` knows its own size.** `count_of` is `.size()`
18//! and `make_room` is `.resize(n)`; C's `_length` and `_buffer` have
19//! no counterpart and neither does its allocation guard, because
20//! `resize` throws rather than returning nothing.
21//! - **A plain pointer for `@optional`.** An absent member is `nullptr`
22//! and presence is `!= nullptr` (XTypes 1.3 §7.5.1.2.4.2); the codec
23//! reaches the value through [`CodecTarget::behind_indirection`] and a
24//! reader makes it with [`CodecTarget::make_indirection`], the seams C
25//! uses for the same member. There is no flag beside the value, for the
26//! reason `adapter/java.rs` gives for Java: a flag beside the value is
27//! two things that can disagree.
28//!
29//! What is *not* different is the wire. `runtime/cpp` implements
30//! DDS-XTypes 1.3 §7.4 from the clause and calls neither the Rust
31//! runtime nor the C one.
32
33use super::{BitPatternSource, CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
34use crate::adapter::cpp::{
35 cpp_holder, cpp_primitive, cpp_type_path, cpp_type_tag, cpp_value_type, cxx_name,
36};
37use crate::encode::EncodeError;
38use zerodds_ast::{Ast, MemberNode, PrimitiveKind, TypeNode};
39use zerodds_ast_core::Id;
40use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
41use zerodds_lang_cst::cpp;
42
43/// The C++ backend.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct Cpp;
46
47impl Cpp {
48 /// A name in the runtime's namespace.
49 fn wire(name: &str) -> Path {
50 Path::new(&["zerodds", "wire", name])
51 }
52
53 /// `zerodds::wire::external_slot(place)` — the runtime function that
54 /// fills an `@external` member's `std::shared_ptr` and hands back a
55 /// reference to what it now points at. See
56 /// `runtime/cpp/include/zerodds/wire.hpp`.
57 fn external_slot(place: cpp::Expr) -> cpp::Expr {
58 cpp::Expr::Call {
59 callee: Box::new(cpp::Expr::Path(Self::wire("external_slot"))),
60 args: vec![place],
61 }
62 }
63}
64
65impl CodecTarget for Cpp {
66 type Expr = cpp::Expr;
67 type Stmt = cpp::Statement;
68 type Block = cpp::Block;
69 type Type = cpp::Type;
70 type Decl = cpp::Decl;
71
72 // -- types --------------------------------------------------------
73
74 /// The fixed-width types, from the materialiser's own table.
75 ///
76 /// `Index` and `Size` are both `std::size_t` here: C++ has an
77 /// unsigned type for each, so the distinction C needs — a `uint32_t`
78 /// counter against a `size_t` position — costs nothing to collapse
79 /// and a `std::vector::size()` returns the latter.
80 fn scalar(kind: Scalar) -> Self::Type {
81 let primitive = match kind {
82 Scalar::Index | Scalar::Size => {
83 return cpp::Type::named(Path::new(&["std", "size_t"]));
84 }
85 Scalar::Bool => PrimitiveKind::Boolean,
86 Scalar::Char => PrimitiveKind::Char,
87 Scalar::Int8 => PrimitiveKind::Int8,
88 Scalar::Int16 => PrimitiveKind::Int16,
89 Scalar::Int32 => PrimitiveKind::Int32,
90 Scalar::Int64 => PrimitiveKind::Int64,
91 Scalar::UInt8 => PrimitiveKind::UInt8,
92 Scalar::UInt16 => PrimitiveKind::UInt16,
93 Scalar::UInt32 => PrimitiveKind::UInt32,
94 Scalar::UInt64 => PrimitiveKind::UInt64,
95 Scalar::Float => PrimitiveKind::Float32,
96 Scalar::Double => PrimitiveKind::Float64,
97 Scalar::LongDouble => PrimitiveKind::Float128,
98 };
99 cpp_primitive(primitive)
100 }
101
102 fn named(name: &str) -> Self::Type {
103 cpp::Type::named(Path::new(&[name]))
104 }
105
106 fn nothing() -> Self::Type {
107 cpp::Type::Void
108 }
109
110 /// The handles are types of the runtime's namespace, not nested in
111 /// the writer: `zerodds::wire::DHeader`.
112 fn handle(kind: Handle) -> Self::Type {
113 cpp::Type::named(Self::wire(match kind {
114 Handle::DHeader => "DHeader",
115 Handle::EmHeader => "EmHeader",
116 Handle::Pid => "Pid",
117 // The read direction's four.
118 Handle::TryConstruct => "TryConstruct",
119 Handle::ReaderMark => "ReaderMark",
120 Handle::PidRead => "PidRead",
121 Handle::EmHeaderRead => "EmHeaderRead",
122 }))
123 }
124
125 fn unsigned_holder(bits: u16) -> Self::Type {
126 cpp_holder(bits)
127 }
128
129 /// A free function of the runtime's namespace.
130 ///
131 /// `zerodds::wire::no_dheader()` and not `Writer::no_dheader()`,
132 /// which is what this used to write. C++ has free functions, so an
133 /// operation the walk describes as having no receiver is one; and
134 /// the writer's qualification was wrong the moment a *reader* asked
135 /// for one — `Writer::pid_is_user_member` names a static member the
136 /// writer has no business declaring.
137 fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
138 cpp::Expr::Call {
139 callee: Box::new(cpp::Expr::Path(Self::wire(method))),
140 args,
141 }
142 }
143
144 /// A policy value, in the runtime's namespace.
145 fn constant(which: Constant) -> Self::Expr {
146 let (type_, value) = match which {
147 Constant::WcharWidth2 => ("WcharWidth", "Two"),
148 Constant::WcharWidth4 => ("WcharWidth", "Four"),
149 Constant::WcharFixed => ("WcharFraming", "Fixed"),
150 Constant::WcharGiopLengthPrefixed => ("WcharFraming", "GiopLengthPrefixed"),
151 Constant::WstringUnitsStream => ("WstringUnitOrder", "Stream"),
152 Constant::WstringUnitsBigEndianAlways => ("WstringUnitOrder", "BigEndianAlways"),
153 Constant::WstringUnitsByteOrderMark => ("WstringUnitOrder", "ByteOrderMark"),
154 Constant::WstringUnitsHostUnswapped => ("WstringUnitOrder", "HostUnswapped"),
155 Constant::WstringSpecification => ("WstringLengthPolicy", "Specification"),
156 Constant::WstringCharacterCount => ("WstringLengthPolicy", "CharacterCount"),
157 Constant::WstringCharacterCountTerminated => {
158 ("WstringLengthPolicy", "CharacterCountTerminated")
159 }
160 Constant::WstringSpecificationWideUnits => {
161 ("WstringLengthPolicy", "SpecificationWideUnits")
162 }
163 Constant::WstringRosTypeSupport => ("WstringLengthPolicy", "RosTypeSupport"),
164 Constant::WstringOctetsTerminated2 => ("WstringLengthPolicy", "OctetsTerminated2"),
165 Constant::WstringOctetsTerminated4 => ("WstringLengthPolicy", "OctetsTerminated4"),
166 Constant::WstringCharactersTerminated2 => {
167 ("WstringLengthPolicy", "CharactersTerminated2")
168 }
169 Constant::FixedDeclared => ("FixedDigitCount", "Declared"),
170 Constant::FixedValuePrecision => ("FixedDigitCount", "ValuePrecision"),
171 Constant::FixedValueStringDigits => ("FixedDigitCount", "ValueStringDigits"),
172 Constant::ParameterMember => ("ParameterRole", "Member"),
173 Constant::ParameterOptional => ("ParameterRole", "Optional"),
174 Constant::TryConstructDiscard => ("TryConstruct", "Discard"),
175 Constant::TryConstructTrim => ("TryConstruct", "Trim"),
176 Constant::TryConstructUseDefault => ("TryConstruct", "UseDefault"),
177 Constant::StatusOk => ("Status", "Ok"),
178 Constant::StatusDiscarded => ("Status", "Discarded"),
179 Constant::StatusImpossibleLength => ("Status", "ImpossibleLength"),
180 Constant::StatusOutOfRange => ("Status", "OutOfRange"),
181 Constant::StatusTruncated => ("Status", "Truncated"),
182 };
183 cpp::Expr::Path(Path::new(&["zerodds", "wire", type_, value]))
184 }
185
186 // -- expressions --------------------------------------------------
187
188 fn name(text: &str) -> Self::Expr {
189 cpp::Expr::Path(Path::new(&text.split("::").collect::<Vec<_>>()))
190 }
191
192 fn int(value: i128) -> Self::Expr {
193 cpp::Expr::Literal(Literal::int(value))
194 }
195
196 fn literal(value: Literal) -> Self::Expr {
197 cpp::Expr::Literal(value)
198 }
199
200 /// `strcmp` is the walk's name for a three-way code-unit
201 /// comparison and is not a name this column's strings answer to:
202 /// the key is a `std::string`, and `a.compare(b)` is
203 /// `char_traits<char>::compare` — the same unsigned octet order
204 /// C11 §7.24.4 gives `strcmp`, with the same sign convention.
205 fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
206 if callee == "strcmp" {
207 let mut arguments = args.into_iter();
208 if let (Some(left), Some(right)) = (arguments.next(), arguments.next()) {
209 return cpp::Expr::MethodCall {
210 receiver: Box::new(left),
211 name: Ident::new("compare"),
212 args: vec![right],
213 };
214 }
215 return cpp::Expr::Call {
216 callee: Box::new(Self::name("strcmp")),
217 args: Vec::new(),
218 };
219 }
220 cpp::Expr::Call {
221 callee: Box::new(Self::name(callee)),
222 args,
223 }
224 }
225
226 fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
227 cpp::Expr::MethodCall {
228 receiver: Box::new(Self::name(receiver)),
229 name: Ident::new(method),
230 args,
231 }
232 }
233
234 /// `runtime/cpp` carries the three HDDS axes: `begin_packed_element`
235 /// and `finish_packed_element` on the writer and the reader, the
236 /// DHEADER pair for a sequence element's own length, and
237 /// `write_wstring_utf8_octets` / `read_wstring_utf8_octets`.
238 fn carries_hdds_axes() -> bool {
239 true
240 }
241
242 /// A reference, so a member is `.` and never `->`.
243 fn member(base: &str, field: &str) -> Self::Expr {
244 cpp::Expr::Member {
245 value: Box::new(Self::name(base)),
246 name: Ident::new(field),
247 }
248 }
249
250 fn whole(base: &str) -> Self::Expr {
251 Self::name(base)
252 }
253
254 /// `UINT64_C` is C's; C++ writes the suffix.
255 ///
256 /// −2^63 takes no suffix, because it is not a literal:
257 /// `-9223372036854775808LL` is a minus over a literal one past
258 /// `LLONG_MAX`. [`zerodds_lang_render::cpp::integer_text`] carries
259 /// the clause and the measurement and answers with the subtraction.
260 /// The result is a parenthesised expression rather than a literal,
261 /// which every site this reaches admits: the walk puts it inside a
262 /// `static_cast` in an `if` condition and never where C++ requires
263 /// a token — the generated union reader is a chain of comparisons
264 /// and carries no `case` label.
265 fn wide_int(value: i128) -> Self::Expr {
266 let text = if value > i128::from(i64::MAX) {
267 format!("{value}ULL")
268 } else if value == i128::from(i64::MIN) {
269 zerodds_lang_render::cpp::integer_text(value)
270 } else if value > i128::from(i32::MAX) || value < i128::from(i32::MIN) {
271 format!("{value}LL")
272 } else {
273 format!("{value}")
274 };
275 cpp::Expr::Literal(Literal::Int(text))
276 }
277
278 fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
279 cpp::Expr::Member {
280 value: Box::new(value),
281 name: Ident::new(field),
282 }
283 }
284
285 /// The storage behind the accessor of a case (`adapter::cpp::union_storage`):
286 /// the codec reads and writes the storage and not the accessor, whose
287 /// setter selects the case.
288 fn case_member(base: &str, field: &str) -> Self::Expr {
289 Self::member(base, &crate::adapter::cpp::union_storage(field))
290 }
291
292 fn discriminant(base: &str) -> Self::Expr {
293 Self::member(base, crate::adapter::cpp::UNION_DISCRIMINATOR)
294 }
295
296 fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
297 cpp::Expr::Index {
298 value: Box::new(value),
299 index: Box::new(at),
300 }
301 }
302
303 /// A `std::vector` and a `std::array` both answer `.size()`, so the
304 /// bound the materialiser fixed is not needed and not used.
305 fn count_of(base: &str, _fixed: Option<u64>) -> Self::Expr {
306 cpp::Expr::MethodCall {
307 receiver: Box::new(Self::name(base)),
308 name: Ident::new("size"),
309 args: Vec::new(),
310 }
311 }
312
313 fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
314 cpp::Expr::Binary {
315 op,
316 lhs: Box::new(lhs),
317 rhs: Box::new(rhs),
318 }
319 }
320
321 /// The tree has no unary node; `!a` is `a == false` and `-a` is
322 /// `0 - a`, which is what both mean.
323 /// `!a` as `a == false`, and every other operator as itself.
324 ///
325 /// The negation is spelled out because `!` on a value the walk
326 /// hands over is `operator!` on whatever type it has, and `== false`
327 /// says the same thing for a `bool` without depending on that. The
328 /// rest go through [`cpp::Expr::Unary`], which is what a complement
329 /// needs: the form this used before wrote `0 ~ a`, which no
330 /// compiler reads.
331 fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
332 match op {
333 "!" => cpp::Expr::Binary {
334 op: "==",
335 lhs: Box::new(value),
336 rhs: Box::new(cpp::Expr::Literal(Literal::Bool(false))),
337 },
338 _ => cpp::Expr::Unary {
339 op,
340 value: Box::new(value),
341 },
342 }
343 }
344
345 /// The bracket is written, because the walk asked for it.
346 ///
347 /// This returned the expression unchanged, on the reading that "the
348 /// renderer brackets by precedence". It does not — `zerodds-lang-cst`
349 /// says so in as many words, on `Expr::Paren`: "this tree holds no
350 /// operator precedence, so a renderer walking it cannot know where a
351 /// bracket is needed. A builder that needs one says so."
352 ///
353 /// What that cost: a bitmask's reader asks for
354 /// `paren(zd_bits & ~UINT64_C(131)) != 0` and got
355 /// `zd_bits & ~UINT64_C(131) != 0`, which C++ groups as
356 /// `zd_bits & (~131 != 0)` — a different number, and one that would
357 /// have accepted every undeclared bit. `-Wparentheses -Werror` made
358 /// it a build failure rather than a wrong value, which is the only
359 /// reason it was found at the compiler and not in a byte comparison.
360 fn paren(value: Self::Expr) -> Self::Expr {
361 cpp::Expr::Paren(Box::new(value))
362 }
363
364 fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
365 cpp::Expr::Cast {
366 kind: cpp::CastKind::Static,
367 type_: Box::new(type_),
368 value: Box::new(value),
369 }
370 }
371
372 /// A reference takes the value either way: there is no address to
373 /// take and no array to decay.
374 fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
375 value
376 }
377
378 /// `write_char` takes a `char32_t`, so the code point is a cast to
379 /// it and the runtime keeps what the width allows.
380 fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
381 cpp::Expr::Cast {
382 kind: cpp::CastKind::Static,
383 type_: Box::new(cpp::Type::named(Path::new(&["char32_t"]))),
384 value: Box::new(value),
385 }
386 }
387
388 /// The integer form is two `static_cast`s — through the unsigned
389 /// type of the source's width first, which is what masks a negative
390 /// value to its two's-complement octets instead of sign-extending
391 /// it — and the float forms are the runtime's `bit_pattern_f32` and
392 /// `bit_pattern_f64`, because C++ has no expression that reads a
393 /// float's bits without `std::memcpy`.
394 fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
395 let through = |narrow: &str, value: cpp::Expr| cpp::Expr::Cast {
396 kind: cpp::CastKind::Static,
397 type_: Box::new(cpp::Type::named(Path::new(&["std", "uint64_t"]))),
398 value: Box::new(cpp::Expr::Cast {
399 kind: cpp::CastKind::Static,
400 type_: Box::new(cpp::Type::named(Path::new(&["std", narrow]))),
401 value: Box::new(value),
402 }),
403 };
404 match source {
405 BitPatternSource::Int { octets } => {
406 let narrow = match octets {
407 1 => "uint8_t",
408 2 => "uint16_t",
409 4 => "uint32_t",
410 _ => "uint64_t",
411 };
412 through(narrow, value)
413 }
414 BitPatternSource::Bool => cpp::Expr::Cast {
415 kind: cpp::CastKind::Static,
416 type_: Box::new(cpp::Type::named(Path::new(&["std", "uint64_t"]))),
417 value: Box::new(value),
418 },
419 BitPatternSource::Float32 => Self::runtime_static("bit_pattern_f32", vec![value]),
420 BitPatternSource::Float64 => Self::runtime_static("bit_pattern_f64", vec![value]),
421 }
422 }
423
424 // -- statements ---------------------------------------------------
425
426 fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
427 cpp::Statement::Declare {
428 type_,
429 name: Ident::new(name),
430 value,
431 }
432 }
433
434 fn perform(value: Self::Expr) -> Self::Stmt {
435 cpp::Statement::Expr(value)
436 }
437
438 fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
439 cpp::Statement::Return(value)
440 }
441
442 fn when(
443 condition: Self::Expr,
444 then: Self::Block,
445 otherwise: Option<Self::Block>,
446 ) -> Self::Stmt {
447 cpp::Statement::If {
448 condition,
449 then,
450 otherwise,
451 }
452 }
453
454 /// `for (std::size_t i = 0; i < bound; i = i + 1)`, the step written
455 /// out for the reason C's is: the tree has no increment node, and
456 /// one spelling across the backends keeps two columns' generated
457 /// source comparable by eye when their octets disagree.
458 fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
459 cpp::Statement::For {
460 init: Box::new(cpp::Statement::Declare {
461 type_: cpp::Type::named(Path::new(&["std", "size_t"])),
462 name: Ident::new(counter),
463 value: Some(cpp::Expr::Literal(Literal::int(0))),
464 }),
465 condition: cpp::Expr::Binary {
466 op: "<",
467 lhs: Box::new(Self::name(counter)),
468 rhs: Box::new(bound),
469 },
470 step: cpp::Expr::Binary {
471 op: "=",
472 lhs: Box::new(Self::name(counter)),
473 rhs: Box::new(cpp::Expr::Binary {
474 op: "+",
475 lhs: Box::new(Self::name(counter)),
476 rhs: Box::new(cpp::Expr::Literal(Literal::int(1))),
477 }),
478 },
479 body,
480 }
481 }
482
483 fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
484 cpp::Statement::While { condition, body }
485 }
486
487 fn leave() -> Self::Stmt {
488 cpp::Statement::Break
489 }
490
491 fn note(doc: Doc) -> Self::Stmt {
492 cpp::Statement::Comment(doc)
493 }
494
495 /// `v.x != nullptr`. XTypes 1.3 §7.5.1.2.4.2: "A NULL pointer shall
496 /// indicate an omitted value."
497 fn present(base: &str, field: &str) -> Self::Expr {
498 cpp::Expr::Binary {
499 op: "!=",
500 lhs: Box::new(Self::member(base, field)),
501 rhs: Box::new(Self::name("nullptr")),
502 }
503 }
504
505 /// Nothing on the way in — [`Cpp::make_indirection`] makes the object
506 /// — and `zerodds::wire::destroy(v.x);` on the way out, which deletes
507 /// what the member held and leaves it NULL.
508 fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
509 if present {
510 return Vec::new();
511 }
512 vec![cpp::Statement::Expr(cpp::Expr::Call {
513 callee: Box::new(cpp::Expr::Path(Self::wire("destroy"))),
514 args: vec![Self::member(base, field)],
515 })]
516 }
517
518 /// An `@optional` member is a plain pointer (§7.5.1.2.4.2).
519 fn optionals_are_pointers() -> bool {
520 true
521 }
522
523 /// A `std::string` is a value, so an `@optional string` is a
524 /// `std::string *` like any other.
525 fn value_is_pointer(_ast: &Ast, _id: Id<TypeNode>) -> bool {
526 false
527 }
528
529 /// `if (!(v.x != nullptr)) { throw zerodds::wire::UnsetExternal(...); }`
530 fn refuse_unset_external(present: Self::Expr) -> Vec<Self::Stmt> {
531 vec![cpp::Statement::If {
532 condition: cpp::Expr::Unary {
533 op: "!",
534 value: Box::new(cpp::Expr::Paren(Box::new(present.clone()))),
535 },
536 then: cpp::Block {
537 statements: vec![cpp::Statement::Throw(cpp::Expr::Construct {
538 type_: Box::new(cpp::Type::named(Self::wire("UnsetExternal"))),
539 args: vec![
540 cpp::Expr::MethodCall {
541 receiver: Box::new(Self::name("w")),
542 name: Ident::new("position"),
543 args: Vec::new(),
544 },
545 cpp::Expr::Literal(Literal::string(&cpp_text_of(&present))),
546 ],
547 braced: false,
548 })],
549 },
550 otherwise: None,
551 }]
552 }
553
554 /// The vector is the elements.
555 /// `.value()` and not `*`: the two are the same inside a guarded
556 /// branch and the first says which one it is.
557 /// A `std::pair`, so `first` and `second`.
558 fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
559 cpp::Expr::Member {
560 value: Box::new(entry),
561 name: Ident::new(if key { "first" } else { "second" }),
562 }
563 }
564
565 /// The member, the pointer; [`Cpp::behind_indirection`] dereferences it.
566 fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
567 Self::member(base, field)
568 }
569
570 /// `*place` — the object an optional pointer points at.
571 fn behind_indirection(_ast: &Ast, _id: Id<TypeNode>, place: Self::Expr) -> Self::Expr {
572 cpp::Expr::Deref(Box::new(place))
573 }
574
575 /// `zerodds::wire::pointer_slot(place)` — the pointer made to point at
576 /// a new object when it points nowhere, and the object.
577 fn make_indirection(
578 _ast: &Ast,
579 _id: Id<TypeNode>,
580 place: Self::Expr,
581 ) -> (Vec<Self::Stmt>, Self::Expr) {
582 (
583 Vec::new(),
584 cpp::Expr::Call {
585 callee: Box::new(cpp::Expr::Path(Self::wire("pointer_slot"))),
586 args: vec![place],
587 },
588 )
589 }
590
591 /// `true`: an `@external` member is a `std::shared_ptr`, and a member on a
592 /// cycle needs the same: `std::optional<T>` and a plain `T` both want
593 /// `T` complete.
594 /// [`crate::adapter::held_externally`] is the rule the adapter and
595 /// the defaulter ask as well.
596 fn holds_a_cycle_externally() -> bool {
597 true
598 }
599
600 /// `*v.x` — the shared pointer dereferenced, for `@external` with
601 /// and without `@optional` alike: the member's codec takes the
602 /// value, and `crates/lang-rules/src/adapter/cpp.rs` gives an
603 /// `@external` member one indirection and no `std::optional` around
604 /// it.
605 fn external_value(
606 _ast: &Ast,
607 _id: Id<TypeNode>,
608 base: &str,
609 field: &str,
610 _optional: bool,
611 ) -> Self::Expr {
612 cpp::Expr::Unary {
613 op: "*",
614 value: Box::new(Self::member(base, field)),
615 }
616 }
617
618 /// `zerodds::wire::external_slot(v.x)` — the slot filled and the
619 /// place handed back, in one expression.
620 ///
621 /// The counterpart of [`Cpp::optional_destination`]'s `emplace`: a
622 /// pointer member has nothing to write into until it points at
623 /// something. A function of the runtime and not
624 /// `std::make_shared<T>()` written here, because this seam is handed
625 /// the member and not its type.
626 fn external_destination(
627 base: &str,
628 field: &str,
629 _optional: bool,
630 ) -> (Vec<Self::Stmt>, Self::Expr) {
631 (Vec::new(), Self::external_slot(Self::member(base, field)))
632 }
633
634 /// `*v.x` for a branch too: a union arm's `@external` field is the
635 /// same `std::shared_ptr<T>` a structure member is.
636 fn external_case_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
637 Self::external_value(
638 _ast,
639 _id,
640 base,
641 &crate::adapter::cpp::union_storage(field),
642 false,
643 )
644 }
645
646 /// The same allocation and the same place as
647 /// [`Cpp::external_destination`].
648 fn external_case_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
649 Self::external_destination(base, &crate::adapter::cpp::union_storage(field), false)
650 }
651
652 /// `v.x != nullptr`: the pointer is the absence, with no
653 /// `std::optional` around it to carry a second one.
654 fn external_present(base: &str, field: &str) -> Self::Expr {
655 cpp::Expr::Binary {
656 op: "!=",
657 lhs: Box::new(Self::member(base, field)),
658 rhs: Box::new(Self::name("nullptr")),
659 }
660 }
661
662 /// Nothing on the way in — [`Cpp::external_destination`] allocates —
663 /// and `v.x = nullptr;` on the way out.
664 fn external_set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
665 if present {
666 return Vec::new();
667 }
668 vec![cpp::Statement::Expr(cpp::Expr::Binary {
669 op: "=",
670 lhs: Box::new(Self::member(base, field)),
671 rhs: Box::new(Self::name("nullptr")),
672 })]
673 }
674
675 /// `v[i]`, except for the one container C++ specialises.
676 ///
677 /// `std::vector<bool>` packs its elements into bits — [vector.bool] —
678 /// and `operator[]` hands back a proxy object rather than a
679 /// `bool &`. A generated decoder takes its destination by reference,
680 /// so there is nothing for it to point at, and the call does not
681 /// compile. Read into a local and assign it back: one statement
682 /// either side, and only where the element really is `bool`.
683 ///
684 /// Not solved by changing what a `sequence<boolean>` maps to. The
685 /// element codec is named by the IDL kind — `zd_dec_p_boolean` —
686 /// takes a `bool &`, and is the same function whether the value is a
687 /// member, an array element or a map's value, so a container holding
688 /// anything but `bool` would not match it either. The proxy is the
689 /// container's, so the work-around belongs at the container.
690 fn sequence_element(
691 base: &str,
692 index: Self::Expr,
693 element: Self::Type,
694 ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
695 let slot = cpp::Expr::Index {
696 value: Box::new(Self::elements(base)),
697 index: Box::new(index),
698 };
699 if element != cpp::Type::Primitive(cpp::Primitive::Bool) {
700 return (Vec::new(), slot, Vec::new());
701 }
702 let local = "zd_bit";
703 (
704 vec![cpp::Statement::Declare {
705 type_: cpp::Type::Primitive(cpp::Primitive::Bool),
706 name: Ident::new(local),
707 value: Some(cpp::Expr::Literal(Literal::Bool(false))),
708 }],
709 Self::name(local),
710 vec![cpp::Statement::Expr(cpp::Expr::Binary {
711 op: "=",
712 lhs: Box::new(slot),
713 rhs: Box::new(Self::name(local)),
714 })],
715 )
716 }
717
718 /// `v.resize(count);`, which is `make_room` again in the shortening
719 /// direction. A `std::vector` owns its elements, so the ones past
720 /// the bound are destroyed rather than left where C leaves them.
721 fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
722 vec![cpp::Statement::Expr(cpp::Expr::MethodCall {
723 receiver: Box::new(Self::name(base)),
724 name: Ident::new("resize"),
725 args: vec![count],
726 })]
727 }
728
729 /// The member: [`Cpp::make_indirection`] makes it point somewhere.
730 fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
731 (Vec::new(), Self::member(base, field))
732 }
733
734 fn elements(base: &str) -> Self::Expr {
735 Self::name(base)
736 }
737
738 /// `w.write_octets(v.data(), n)`: `std::vector<std::uint8_t>` and
739 /// `std::array<std::uint8_t, N>` both hold their octets contiguously.
740 fn bulk_octets_write(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
741 Some(Self::perform(Self::runtime(
742 "w",
743 "write_octets",
744 vec![data_of(base), count],
745 )))
746 }
747
748 /// `r.read_octets(v.data(), n)`, after `make_room` has resized `v`.
749 fn bulk_octets_read(base: &str, count: Self::Expr) -> Option<Self::Stmt> {
750 Some(Self::perform(Self::runtime(
751 "r",
752 "read_octets",
753 vec![data_of(base), count],
754 )))
755 }
756
757 /// One statement: `resize` throws rather than returning nothing, so
758 /// there is no guard and the fill follows unconditionally.
759 fn make_room(
760 base: &str,
761 _element: Self::Type,
762 count: Self::Expr,
763 // C's slot is storage the moment the buffer is there, so
764 // whether the fill assigns it or writes into it makes no
765 // difference to what has to be allocated.
766 _assigned_whole: bool,
767 fill: Vec<Self::Stmt>,
768 ) -> Vec<Self::Stmt> {
769 let mut out = vec![cpp::Statement::Expr(cpp::Expr::MethodCall {
770 receiver: Box::new(Self::name(base)),
771 name: Ident::new("resize"),
772 args: vec![count],
773 })];
774 out.extend(fill);
775 out
776 }
777
778 fn absent() -> Self::Expr {
779 cpp::Expr::Path(Path::new(&["nullptr"]))
780 }
781
782 /// Nothing to check. Every value this column's reader materialises
783 /// is a `std::string` or a `std::vector`, which own their storage
784 /// and are never absent; the read either produced one or threw.
785 fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
786 None
787 }
788
789 // -- assembly -----------------------------------------------------
790
791 fn block(statements: Vec<Self::Stmt>) -> Self::Block {
792 cpp::Block { statements }
793 }
794
795 fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
796 block.statements
797 }
798
799 /// `static void zd_enc_T(zerodds::wire::Writer &w, const T &v)`.
800 ///
801 /// A prototype, unlike Java's, is emitted: C++ resolves a call to a
802 /// free function by what is declared above it, so a recursive or
803 /// mutually recursive type needs the declaration ahead of the body
804 /// exactly as C does.
805 fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
806 let stream = cpp::Param {
807 type_: cpp::Type::Ref(Box::new(cpp::Type::named(Self::wire(if spec.writing {
808 "Writer"
809 } else {
810 "Reader"
811 })))),
812 name: Some(Ident::new(if spec.writing { "w" } else { "r" })),
813 default: None,
814 };
815 let mut params = vec![
816 stream,
817 cpp::Param {
818 // `const` in the written direction and not in the read
819 // one, the same distinction C makes with its pointer.
820 type_: cpp::Type::Ref(Box::new(if spec.writing {
821 cpp::Type::Const(Box::new(spec.sample))
822 } else {
823 spec.sample
824 })),
825 name: Some(Ident::new("v")),
826 default: None,
827 },
828 ];
829 params.extend(spec.extra.into_iter().map(|(type_, name)| cpp::Param {
830 type_,
831 name: Some(Ident::new(&name)),
832 default: None,
833 }));
834 // `(void)v;` and `(void)w;` ahead of the body, for the reason
835 // the C reader writes `(void)zd_tc;`: an aggregate with no
836 // members has nothing to write, and `-Wunused-parameter
837 // -Werror` fails the build on the one case that is legal C++
838 // and empty. Emitted unconditionally rather than from a guess
839 // about which bodies touch which parameter.
840 //
841 // The stream was left out of that until an empty `@appendable`
842 // structure was read under XCDR1, where the type takes no
843 // delimiter either — so the body was empty in both directions
844 // and neither parameter was touched. The C column cannot reach
845 // it: C23 §6.7.2.1p8 leaves a structure with no named member
846 // undefined and the C materialiser refuses one.
847 let stream = if spec.writing { "w" } else { "r" };
848 let body = spec.body.map(|block| {
849 let mut statements = vec![
850 cpp::Statement::Expr(cpp::Expr::Cast {
851 kind: cpp::CastKind::Static,
852 type_: Box::new(cpp::Type::Void),
853 value: Box::new(Self::name(stream)),
854 }),
855 cpp::Statement::Expr(cpp::Expr::Cast {
856 kind: cpp::CastKind::Static,
857 type_: Box::new(cpp::Type::Void),
858 value: Box::new(Self::name("v")),
859 }),
860 ];
861 statements.extend(Self::into_statements(block));
862 Self::block(statements)
863 });
864 Some(cpp::Decl::Function(cpp::Function {
865 doc: spec.doc,
866 returns: cpp::Type::Void,
867 name: Ident::new(&spec.name),
868 params,
869 is_inline: false,
870 is_static: true,
871 is_constexpr: false,
872 is_noexcept: false,
873 // `CodecTarget::routine`'s `tolerate_unused`, which
874 // `crate::emit::encode::maybe_unused` sets for the one codec
875 // that may have no caller: a `wchar` whose element form is
876 // written inline under TAO's framing. g++ refused the probe
877 // with `-Werror=unused-function` on `zd_enc_p_wchar` in 48
878 // matrix cells (TAO dialect, every case with a `wchar` run).
879 is_maybe_unused: spec.tolerate_unused,
880 // A body absent is a declaration and not an empty one: C++
881 // resolves a call to a free function by what is declared
882 // above it.
883 body: body.map_or(cpp::FunctionBody::None, cpp::FunctionBody::Block),
884 }))
885 }
886
887 // -- the materialiser's boundary ----------------------------------
888
889 fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
890 cpp_value_type(ast, id, "codec").unwrap_or_else(|_| {
891 cpp::Type::named(Path::new(
892 &[format!("UnresolvedType{}", id.slot()).as_str()],
893 ))
894 })
895 }
896
897 fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
898 cpp_type_tag(ast, id)
899 }
900
901 fn escape(name: &str) -> String {
902 cxx_name(name)
903 }
904
905 fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
906 cpp_type_tag(ast, id)
907 }
908
909 /// `std::pair<K, V>` — the type the materialiser spells the entry
910 /// as. The tag `map_entry_name` answers is a *name* for generated
911 /// identifiers (`zd_dec_map_string_long`), not a declared type, so
912 /// the default's `named(tag)` declared the scratch entry with an
913 /// identifier no compiler finds.
914 fn map_entry_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
915 let Ok(zerodds_ast::TypeKind::Map { key, value, .. }) =
916 ast.type_(id).map(|node| node.kind.clone())
917 else {
918 return Self::named(&Self::map_entry_name(ast, id));
919 };
920 let at = cpp_type_tag(ast, id);
921 let half = |half: Option<Id<TypeNode>>| {
922 half.and_then(|id| cpp_value_type(ast, id, &at).ok())
923 .unwrap_or(cpp::Type::Void)
924 };
925 cpp::Type::template(
926 Path::new(&["std", "pair"]),
927 vec![half(key.resolved()), half(value.resolved())],
928 )
929 }
930
931 /// C++ has namespaces, so a module needs no prefix in a name.
932 fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
933 String::new()
934 }
935
936 /// `::a::b::Colour::red`.
937 ///
938 /// The enumeration's own name is in the middle, which is what makes
939 /// this a seam rather than a prefix and a bare identifier:
940 /// `adapter/cpp.rs` declares a **scoped** enumeration, so an
941 /// enumerator is a member of the type. Its reason is written there —
942 /// an unscoped `enum` would put every enumerator of every
943 /// enumeration of a module in one namespace, where two IDL-legal
944 /// names collide.
945 fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, name: &str) -> Self::Expr {
946 Self::name(&enumerator_constant_name(ast, id, name))
947 }
948
949 /// A call to the generated defaulter, as the C column does it.
950 ///
951 /// Not `place = T{}`. Value-initialisation is not Table 9 — an
952 /// enumeration whose first literal is not zero, a union's selected
953 /// branch and a member's own `@default` are three rows it gets wrong
954 /// — and two columns answering the same table by different
955 /// mechanisms is a divergence no byte comparison shows, because both
956 /// produce *a* value. [`crate::default_cpp`] carries the whole
957 /// argument.
958 ///
959 /// # Errors
960 /// Never here. The defaulter is a call, and whether the type has one
961 /// is settled where the function is built —
962 /// [`crate::default_cpp::cpp_defaulters_called_by`] reads the calls
963 /// this emitted and refuses there, by name.
964 fn default_into(
965 ast: &Ast,
966 id: Id<TypeNode>,
967 place: Self::Expr,
968 ) -> Result<Self::Stmt, EncodeError> {
969 Ok(crate::default_cpp::default_into(ast, id, place))
970 }
971
972 /// # Errors
973 /// [`EncodeError::NoWriter`] where a `@default` names a value C++
974 /// cannot spell — a `long double` literal is the one the corpus
975 /// reaches.
976 fn member_default(
977 ast: &Ast,
978 node: &MemberNode,
979 type_: Id<TypeNode>,
980 place: Self::Expr,
981 ) -> Result<Vec<Self::Stmt>, EncodeError> {
982 crate::default_cpp::member_default(ast, node, type_, place)
983 }
984}
985
986/// The scoped name of one enumerator, as text.
987///
988/// The enumeration's own name is in the middle, which is what makes
989/// this more than a prefix and a bare identifier: `adapter/cpp.rs`
990/// declares a **scoped** enumeration, so an enumerator is a member of
991/// the type. `default_cpp` builds a path from this, and assembling the
992/// name a second time in that file would be a second place the C++
993/// spelling is decided.
994pub fn enumerator_constant_name(ast: &Ast, id: Id<TypeNode>, name: &str) -> String {
995 let mut path = cpp_type_path(ast, id)
996 .segments()
997 .iter()
998 .map(|segment| segment.as_str().to_string())
999 .collect::<Vec<_>>()
1000 .join("::");
1001 path.push_str("::");
1002 path.push_str(&cxx_name(name));
1003 format!("::{path}")
1004}
1005
1006/// `base.data()`: the first element of a contiguous collection.
1007fn data_of(base: &str) -> cpp::Expr {
1008 cpp::Expr::MethodCall {
1009 receiver: Box::new(Cpp::name(base)),
1010 name: Ident::new("data"),
1011 args: Vec::new(),
1012 }
1013}
1014
1015/// The member's name in a presence test the walk built: `v.x != nullptr`
1016/// gives `x`. Only for the text of a diagnostic.
1017fn cpp_text_of(present: &cpp::Expr) -> String {
1018 if let cpp::Expr::Binary { lhs, .. } = present {
1019 if let cpp::Expr::Member { name, .. } = lhs.as_ref() {
1020 return name.as_str().to_string();
1021 }
1022 }
1023 String::new()
1024}