Skip to main content

zerodds_lang_rules/emit/
ada.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The Ada backend of the shared codec walk.
4//!
5//! DDS-XTypes 1.3 §7.4 from the clause, as `runtime/ada` implements it —
6//! the same division as every other column: the walk decides which
7//! member goes out under which header, this file decides how that is
8//! written in Ada, and `runtime/ada` decides what the octets are.
9//!
10//! # The five decisions this column turns on
11//!
12//! **A discriminant is not assignable.** Ada 2012 RM 3.7.1: the
13//! discriminant of a record changes only when the whole record is
14//! assigned. The walk decodes a union's discriminator straight into
15//! `v._d`, so [`Ada::read_call`] routes that one place through a
16//! `declare` block — a constrained object of the decoder's own sample
17//! subtype (`Zd_Sample`, declared by [`Ada::routine`]), assigned whole.
18//! Every branch after it is written under the chain's own `v.D =`
19//! guard, where the discriminant check passes.
20//!
21//! **An `@optional` member is a discriminated record too.** The
22//! materialiser's `Zd_Opt_…` carries `Present` and, in its `True` arm,
23//! `Value`; both change together or not at all, so a decoded value is
24//! assigned as the aggregate `(Present => True, Value => …)` and
25//! absence as `(Present => False)`.
26//!
27//! **A sequence is an access to an unconstrained array**, and the array
28//! type's name is not derivable from the element's, so
29//! [`Ada::make_room`] and [`Ada::keep_first`] allocate through the
30//! fixed name `Zd_Own_Array` — a subtype [`Ada::routine`] declares in
31//! any decoder whose sample is a collection, naming the materialiser's
32//! `…_Array` type.
33//!
34//! **An enumeration converts through its representation.** Ada's type
35//! conversion does not cross the enum/integer line; `Mark'Enum_Val`
36//! turns a wire value into the literal and `Mark'Enum_Rep` the literal
37//! into its code (RM 13.4 in Ada 2022; GNAT implements both under
38//! `-gnat2012`). [`Ada::cast`] spells the first wherever the target
39//! type is an enumeration, and [`Ada::routine`] rewrites the walk's
40//! `Int64` cast of the sample into the second inside an enumeration's
41//! own encoder.
42//!
43//! **Every numeric literal is universal.** Ada's integer literals are
44//! `universal_integer` and convert to any integer type in context, so
45//! [`Ada::wide_int`] and its siblings write the bare number; the width
46//! suffixes the C family needs have no Ada counterpart and no gap to
47//! cover.
48//!
49//! # What is refused rather than bent
50//!
51//! A union arm of an enumeration type under the Cyclone negative-label
52//! overlay would need the arm's representation from a seam that only
53//! carries the expression; the generated `Interfaces.Integer_64 (…)`
54//! conversion is then illegal Ada and GNAT refuses the probe loudly —
55//! a compile error, never wrong octets. Recursive `@optional` and
56//! `@external` members (the materialiser's `Zd_Ref_…` access types)
57//! take the same road: the codec spells the inline accessor, and GNAT
58//! names the missing component.
59
60use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
61use crate::adapter::ada::{ada_holder, ada_map_entry_name, ada_type_name, ada_value_path};
62use crate::default_common::resolve_alias;
63use crate::encode::EncodeError;
64use zerodds_ast::{Ast, MemberNode, PrimitiveKind, TypeKind, TypeNode};
65use zerodds_ast_core::Id;
66use zerodds_lang_cst::ada;
67use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
68
69/// The Ada backend.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct Ada;
72
73/// The package the generated code names the runtime under.
74const RUNTIME: &str = "Zd_Wire";
75
76/// The type name [`CodecTarget::named`] is handed for C's signed
77/// `char`; the Ada member is an `Interfaces.Unsigned_8` and the signed
78/// reading goes through the runtime's `As_Signed`, because an Ada
79/// conversion checks the range where C reinterprets the bits.
80const SIGNED_CHAR: &str = "signed char";
81
82/// The subtype [`Ada::routine`] declares for a decoder's own sample
83/// type, so the one place that has to name it — the discriminant
84/// assignment — can.
85const OWN_SAMPLE: &str = "Zd_Sample";
86
87/// The subtype [`Ada::routine`] declares for the array behind a
88/// collection decoder's own access type, so the allocators can name it.
89const OWN_ARRAY: &str = "Zd_Own_Array";
90
91/// A type, as this column holds it: the subtype mark plus the four
92/// properties the seams cannot re-derive without the model.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct AdaType {
95    /// The subtype mark, dotted.
96    pub mark: String,
97    /// Whether the type resolves to an enumeration — conversions go
98    /// through `'Enum_Val` / `'Enum_Rep`.
99    pub is_enum: bool,
100    /// Whether it is `Boolean` — an integer-literal cast becomes the
101    /// Boolean literal.
102    pub is_bool: bool,
103    /// Whether it resolves to a union — the decoder needs `Zd_Sample`.
104    pub is_union: bool,
105    /// The `…_Array` type behind it, where it is a sequence or a map —
106    /// the decoder needs `Zd_Own_Array`.
107    pub own_array: Option<String>,
108}
109
110impl AdaType {
111    fn bare(mark: &str) -> Self {
112        Self {
113            mark: mark.to_string(),
114            is_enum: false,
115            is_bool: false,
116            is_union: false,
117            own_array: None,
118        }
119    }
120}
121
122/// An expression, with the two properties the walk's seams cannot
123/// carry: whether the place it names is an `@optional` member's wrapper
124/// record, or a union's discriminant.
125#[derive(Debug, Clone)]
126pub enum AExpr {
127    /// An ordinary expression.
128    Plain(ada::Expr),
129    /// The `Zd_Opt_…` field of an `@optional` member, as a place a
130    /// decoded value is about to be written into.
131    OptionalSlot(ada::Expr),
132    /// `v.D`, as a place — assignable only through the whole record.
133    Discriminant(ada::Expr),
134    /// The access-typed field of a member held by reference
135    /// ([`Ada::holds_by_reference`]), as a place a decoded value is
136    /// about to be allocated for; `mark` is the designated subtype the
137    /// allocator names.
138    ReferenceSlot {
139        /// The field.
140        place: ada::Expr,
141        /// `T` in `new T'(…)`.
142        mark: Path,
143    },
144    /// An assignment the walk built through `binary("=", …)`, kept
145    /// apart so [`Ada::perform`] can see which place it writes.
146    Assignment {
147        /// The place.
148        place: Box<AExpr>,
149        /// The value.
150        value: ada::Expr,
151    },
152}
153
154/// The expression inside, whichever form it took.
155///
156/// zerodds-lint: recursion-depth 64 (an assignment's place, which is
157/// itself an `AExpr`). The value walked is owned and moved in, so it
158/// holds no cycle; the walk builds no `AExpr::Assignment` inside
159/// another, so the real depth is one.
160fn inner(expr: AExpr) -> ada::Expr {
161    match expr {
162        AExpr::Plain(e) | AExpr::OptionalSlot(e) | AExpr::Discriminant(e) => e,
163        AExpr::ReferenceSlot { place, .. } => place,
164        // Never a rendered position; rebuilt as the walk spelled it so a
165        // misuse is visible in the output rather than lost.
166        AExpr::Assignment { place, value } => ada::Expr::Binary {
167            op: "=",
168            lhs: Box::new(inner(*place)),
169            rhs: Box::new(value),
170        },
171    }
172}
173
174fn plain(e: ada::Expr) -> AExpr {
175    AExpr::Plain(e)
176}
177
178/// A dotted mark as the tree's path.
179fn mark_path(mark: &str) -> Path {
180    Path::new(&mark.split('.').collect::<Vec<&str>>())
181}
182
183impl Ada {
184    /// A runtime name, qualified: `Zd_Wire.X`.
185    fn runtime_path(name: &str) -> Path {
186        Path::new(&[RUNTIME, name])
187    }
188
189    fn runtime_constant(name: &str) -> AExpr {
190        plain(ada::Expr::Name(Self::runtime_path(name)))
191    }
192
193    /// A runtime operation's Ada spelling: `write_u32` is `Write_U32`.
194    fn operation(method: &str) -> String {
195        ada::ada_case(method)
196    }
197
198    fn selected(value: ada::Expr, name: &str) -> ada::Expr {
199        ada::Expr::Selected {
200            value: Box::new(value),
201            name: Ident::new(name),
202        }
203    }
204
205    fn call_path(path: Path, args: Vec<ada::Expr>) -> ada::Expr {
206        ada::Expr::Call { name: path, args }
207    }
208
209    /// `Natural (value)` — the conversion every count and every shift
210    /// distance crosses on its way into a `Natural` position.
211    fn to_natural(value: ada::Expr) -> ada::Expr {
212        ada::Expr::Convert {
213            mark: Path::single("Natural"),
214            value: Box::new(value),
215        }
216    }
217
218    /// `(Present => True, Value => decoded)` — how a value enters an
219    /// `@optional` member's wrapper record: both halves in one
220    /// assignment, because the discriminated record admits no other
221    /// (RM 3.7.1).
222    fn engaged(value: ada::Expr) -> ada::Expr {
223        ada::Expr::Aggregate(vec![
224            ada::Association {
225                choice: Ident::new(crate::adapter::ada::PRESENT),
226                value: ada::Expr::Literal(Literal::Bool(true)),
227            },
228            ada::Association {
229                choice: Ident::new(crate::adapter::ada::VALUE),
230                value,
231            },
232        ])
233    }
234
235    /// The record a discriminant place belongs to: `v.D`'s prefix.
236    fn discriminant_record(place: ada::Expr) -> ada::Expr {
237        match place {
238            ada::Expr::Selected { value, .. } => *value,
239            other => other,
240        }
241    }
242
243    /// The whole-record assignment a discriminant change has to be
244    /// (RM 3.7.1): a constrained object of the decoder's own sample
245    /// subtype, built from the decoded value, assigned whole.
246    fn assign_discriminant(place: ada::Expr, value: ada::Expr) -> ada::Stmt {
247        ada::Stmt::Declare {
248            vars: vec![ada::VarDecl {
249                name: Ident::new("zd_nd"),
250                mark: Path::single(OWN_SAMPLE),
251                constraint: Some(value),
252                value: None,
253            }],
254            body: vec![ada::Stmt::Assign {
255                place: Self::discriminant_record(place),
256                value: ada::Expr::name("zd_nd"),
257            }],
258        }
259    }
260}
261
262impl CodecTarget for Ada {
263    type Expr = AExpr;
264    type Stmt = ada::Stmt;
265    type Block = Vec<ada::Stmt>;
266    type Type = AdaType;
267    type Decl = ada::Decl;
268
269    // -- types --------------------------------------------------------
270
271    /// The fixed-width Ada type each scalar local takes. `Index` and
272    /// `Size` are both `Natural`: the reader's positions and counts
273    /// answer in it, and an Ada `for` counter is one anyway.
274    fn scalar(kind: Scalar) -> Self::Type {
275        match kind {
276            Scalar::Bool => AdaType {
277                is_bool: true,
278                ..AdaType::bare("Boolean")
279            },
280            Scalar::Char | Scalar::UInt8 => AdaType::bare("Interfaces.Unsigned_8"),
281            Scalar::Int8 => AdaType::bare("Interfaces.Integer_8"),
282            Scalar::Int16 => AdaType::bare("Interfaces.Integer_16"),
283            Scalar::UInt16 => AdaType::bare("Interfaces.Unsigned_16"),
284            Scalar::Int32 => AdaType::bare("Interfaces.Integer_32"),
285            Scalar::UInt32 => AdaType::bare("Interfaces.Unsigned_32"),
286            Scalar::Int64 => AdaType::bare("Interfaces.Integer_64"),
287            Scalar::UInt64 => AdaType::bare("Interfaces.Unsigned_64"),
288            Scalar::Index | Scalar::Size => AdaType::bare("Natural"),
289            Scalar::Float => AdaType::bare("Interfaces.IEEE_Float_32"),
290            Scalar::Double => AdaType::bare("Interfaces.IEEE_Float_64"),
291            // The decimal text; the adapter's table says why.
292            Scalar::LongDouble => AdaType::bare("Ada.Strings.Unbounded.Unbounded_String"),
293        }
294    }
295
296    fn named(name: &str) -> Self::Type {
297        AdaType::bare(name)
298    }
299
300    /// Never rendered: every value-returning position is typed, and the
301    /// two `(void)` idioms are overridden below. The name is one no
302    /// scope declares, so a use that slips through fails in GNAT.
303    fn nothing() -> Self::Type {
304        AdaType::bare("Zd_No_Type")
305    }
306
307    fn handle(kind: Handle) -> Self::Type {
308        AdaType::bare(match kind {
309            Handle::DHeader => "Zd_Wire.Dheader",
310            Handle::EmHeader => "Zd_Wire.Emheader",
311            Handle::Pid => "Zd_Wire.Pid_Write",
312            Handle::TryConstruct => "Zd_Wire.Try_Construct",
313            Handle::ReaderMark => "Zd_Wire.Reader_Mark",
314            Handle::PidRead => "Zd_Wire.Pid_Read",
315            Handle::EmHeaderRead => "Zd_Wire.Emheader_Read",
316        })
317    }
318
319    /// The member is the code unit — `Unsigned_8` for a `char`,
320    /// `Unsigned_16` for a `wchar` — and the runtime takes the code
321    /// point as `Unsigned_32`, so the conversion widens and never
322    /// narrows.
323    fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
324        plain(ada::Expr::Convert {
325            mark: mark_path("Interfaces.Unsigned_32"),
326            value: Box::new(inner(value)),
327        })
328    }
329
330    /// The runtime's policy values: integers where `runtime/ada` takes
331    /// a width, and its own enumeration literals for everything else.
332    /// One arm per constant and no catch-all.
333    fn constant(which: Constant) -> Self::Expr {
334        match which {
335            Constant::WcharWidth2 => plain(ada::Expr::int(2)),
336            Constant::WcharWidth4 => plain(ada::Expr::int(4)),
337            Constant::WcharFixed => Self::runtime_constant("Wchar_Fixed"),
338            Constant::WcharGiopLengthPrefixed => {
339                Self::runtime_constant("Wchar_Giop_Length_Prefixed")
340            }
341            Constant::WstringUnitsStream => Self::runtime_constant("Unit_Stream"),
342            Constant::WstringUnitsBigEndianAlways => {
343                Self::runtime_constant("Unit_Big_Endian_Always")
344            }
345            Constant::WstringUnitsByteOrderMark => Self::runtime_constant("Unit_Byte_Order_Mark"),
346            Constant::WstringUnitsHostUnswapped => Self::runtime_constant("Unit_Host_Unswapped"),
347            Constant::WstringSpecification => Self::runtime_constant("Wstr_Octets_Unterminated_2"),
348            Constant::WstringCharacterCount => {
349                Self::runtime_constant("Wstr_Characters_Unterminated_2")
350            }
351            Constant::WstringCharacterCountTerminated => {
352                Self::runtime_constant("Wstr_Characters_Terminated_4")
353            }
354            Constant::WstringSpecificationWideUnits => {
355                Self::runtime_constant("Wstr_Octets_Unterminated_4")
356            }
357            Constant::WstringRosTypeSupport => {
358                Self::runtime_constant("Wstr_Characters_Unterminated_4")
359            }
360            Constant::WstringOctetsTerminated2 => {
361                Self::runtime_constant("Wstr_Octets_Terminated_2")
362            }
363            Constant::WstringOctetsTerminated4 => {
364                Self::runtime_constant("Wstr_Octets_Terminated_4")
365            }
366            Constant::WstringCharactersTerminated2 => {
367                Self::runtime_constant("Wstr_Characters_Terminated_2")
368            }
369            Constant::FixedDeclared => Self::runtime_constant("Fixed_Declared"),
370            Constant::FixedValuePrecision => Self::runtime_constant("Fixed_Value_Precision"),
371            Constant::FixedValueStringDigits => Self::runtime_constant("Fixed_Value_String_Digits"),
372            Constant::ParameterMember => Self::runtime_constant("Role_Member"),
373            Constant::ParameterOptional => Self::runtime_constant("Role_Optional"),
374            Constant::TryConstructDiscard => Self::runtime_constant("Tc_Discard"),
375            Constant::TryConstructTrim => Self::runtime_constant("Tc_Trim"),
376            Constant::TryConstructUseDefault => Self::runtime_constant("Tc_Use_Default"),
377            Constant::StatusOk => Self::runtime_constant("Status_Ok"),
378            Constant::StatusDiscarded => Self::runtime_constant("Status_Discarded"),
379            Constant::StatusImpossibleLength => Self::runtime_constant("Status_Impossible_Length"),
380            Constant::StatusOutOfRange => Self::runtime_constant("Status_Out_Of_Range"),
381            Constant::StatusTruncated => Self::runtime_constant("Status_Truncated"),
382        }
383    }
384
385    fn unsigned_holder(bits: u16) -> Self::Type {
386        AdaType::bare(ada_holder(bits).as_str())
387    }
388
389    /// `runtime/ada` carries the three HDDS axes: `Begin_Packed_Element`
390    /// and `Finish_Packed_Element` on the writer and `Reader_Begin_Packed_Element`
391    /// / `Reader_Finish_Packed_Element` on the reader, the DHEADER pair for a
392    /// sequence element's own length, and `Write_Wstring_Utf8_Octets` /
393    /// `Read_Wstring_Utf8_Octets`.
394    fn carries_hdds_axes() -> bool {
395        true
396    }
397
398    fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
399        let mark = match ada_value_path(ast, id) {
400            Ok(path) => path
401                .segments()
402                .iter()
403                .map(Ident::as_str)
404                .collect::<Vec<&str>>()
405                .join("."),
406            // A type with no Ada form was refused by the materialiser
407            // before a codec walk ran; the name makes the impossible
408            // fail in GNAT, loudly.
409            Err(_) => "Zd_No_Ada_Form".to_string(),
410        };
411        let resolved = resolve_alias(ast, id);
412        let kind = ast.type_(resolved).map(|node| node.kind.clone());
413        AdaType {
414            mark,
415            is_enum: matches!(kind, Ok(TypeKind::Enumeration { .. })),
416            is_bool: matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Boolean))),
417            is_union: matches!(kind, Ok(TypeKind::Union { .. })),
418            own_array: match kind {
419                Ok(TypeKind::Sequence { .. } | TypeKind::Map { .. }) => {
420                    Some(format!("{}_Array", ada_type_name(ast, resolved)))
421                }
422                _ => None,
423            },
424        }
425    }
426
427    fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
428        crate::adapter::ada::type_tag(ast, id)
429    }
430
431    fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
432        ada_map_entry_name(ast, id)
433    }
434
435    /// Nothing: a declared type's Ada name is already flat, and an
436    /// enumeration literal resolves by its expected type (RM 3.5.1).
437    fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
438        String::new()
439    }
440
441    /// The materialiser's own spelling of a member, exactly.
442    fn escape(name: &str) -> String {
443        crate::adapter::ada::component_name(name)
444    }
445
446    /// The base is the first member `parent` (XTypes 1.3 §7.5.1.4.1.3).
447    fn inherited_path(depth: usize) -> Option<String> {
448        Some(format!("{}.", crate::adapter::ada::component_name("parent")).repeat(depth))
449    }
450
451    // -- expressions ---------------------------------------------------
452
453    fn name(text: &str) -> Self::Expr {
454        plain(ada::Expr::name(local_name(text)))
455    }
456
457    fn int(value: i128) -> Self::Expr {
458        plain(ada::Expr::int(value))
459    }
460
461    /// The raw tokens the walk escapes through [`Literal::Int`],
462    /// rewritten into Ada's boolean literals where they are one.
463    fn literal(value: Literal) -> Self::Expr {
464        let Literal::Int(text) = &value else {
465            return plain(ada::Expr::Literal(value));
466        };
467        match text.as_str() {
468            "true" => plain(ada::Expr::Literal(Literal::Bool(true))),
469            "false" => plain(ada::Expr::Literal(Literal::Bool(false))),
470            _ => plain(ada::Expr::Literal(value)),
471        }
472    }
473
474    /// The bare number: an Ada integer literal is `universal_integer`
475    /// (RM 2.4) and takes any integer type from context, so no width
476    /// suffix exists to write.
477    fn wide_int(value: i128) -> Self::Expr {
478        Self::int(value)
479    }
480
481    fn unsigned_int(value: i128) -> Self::Expr {
482        Self::int(value)
483    }
484
485    fn wide_unsigned(value: u64) -> Self::Expr {
486        Self::int(i128::from(value))
487    }
488
489    fn wide_signed(value: i128) -> Self::Expr {
490        Self::int(value)
491    }
492
493    /// `v.X` — and `v._d` as the discriminant `D`, marked, because the
494    /// place it names is assignable only through the whole record.
495    fn member(base: &str, field: &str) -> Self::Expr {
496        if field == "_d" {
497            return AExpr::Discriminant(Self::selected(
498                ada::Expr::name(local_name(base)),
499                crate::adapter::ada::DISCRIMINANT,
500            ));
501        }
502        plain(Self::selected(ada::Expr::name(local_name(base)), field))
503    }
504
505    fn whole(base: &str) -> Self::Expr {
506        plain(ada::Expr::name(local_name(base)))
507    }
508
509    fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
510        plain(Self::selected(inner(value), field))
511    }
512
513    /// A header's field, in the spelling `runtime/ada` publishes.
514    /// `end` is `End_Flag`: RM 2.9 reserves the word and a component
515    /// cannot carry it.
516    fn header_field(value: Self::Expr, field: &str) -> Self::Expr {
517        let name = if field == "end" {
518            "End_Flag".to_string()
519        } else {
520            ada::ada_case(field)
521        };
522        plain(Self::selected(inner(value), &name))
523    }
524
525    fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
526        plain(ada::Expr::Indexed {
527            value: Box::new(inner(value)),
528            index: Box::new(inner(at)),
529        })
530    }
531
532    /// `Interfaces.Unsigned_32 (v.all'Length)`, or the bound where the
533    /// materialiser fixed one: a sequence is an access to an
534    /// unconstrained array and its count is the array's own.
535    fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
536        match fixed {
537            Some(bound) => plain(ada::Expr::int(i128::from(bound))),
538            None => plain(ada::Expr::Convert {
539                mark: mark_path("Interfaces.Unsigned_32"),
540                value: Box::new(ada::Expr::Attribute {
541                    value: Box::new(ada::Expr::Deref(Box::new(ada::Expr::name(local_name(
542                        base,
543                    ))))),
544                    name: Ident::new("Length"),
545                    args: Vec::new(),
546                }),
547            }),
548        }
549    }
550
551    /// The access value itself: indexing dereferences implicitly
552    /// (RM 4.1).
553    fn elements(base: &str) -> Self::Expr {
554        plain(ada::Expr::name(local_name(base)))
555    }
556
557    /// `v.X` — the variant part's components sit directly on the
558    /// record.
559    fn case_member(base: &str, field: &str) -> Self::Expr {
560        Self::member(base, field)
561    }
562
563    /// `entry.Key` and `entry.Value`, the two components the
564    /// materialiser gives a map's entry record.
565    fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
566        plain(Self::selected(
567            inner(entry),
568            if key {
569                "Key"
570            } else {
571                crate::adapter::ada::VALUE
572            },
573        ))
574    }
575
576    fn absent() -> Self::Expr {
577        plain(ada::Expr::Literal(Literal::Null))
578    }
579
580    /// Nothing: an `Unbounded_String` owns its storage and there is no
581    /// allocation failure for the guard to test.
582    fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
583        None
584    }
585
586    /// `v.X.Value` — the `True` arm's component. Only ever reached
587    /// inside a branch the presence test guarded, where the
588    /// discriminant check cannot fire.
589    fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
590        plain(Self::selected(
591            inner(Self::member(base, field)),
592            crate::adapter::ada::VALUE,
593        ))
594    }
595
596    /// The adapter's own answer: `crate::adapter::ada::held_by_reference`.
597    fn holds_by_reference(
598        ast: &Ast,
599        aggregate: Id<TypeNode>,
600        member_type: Id<TypeNode>,
601        optional: bool,
602        external: bool,
603    ) -> bool {
604        crate::adapter::ada::held_by_reference(ast, aggregate, member_type, optional, external)
605    }
606
607    /// `v.X /= null`.
608    fn reference_present(base: &str, field: &str) -> Self::Expr {
609        Self::binary(
610            "!=",
611            Self::member(base, field),
612            plain(ada::Expr::Literal(Literal::Null)),
613        )
614    }
615
616    /// `v.X.all`.
617    fn reference_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
618        plain(ada::Expr::Deref(Box::new(inner(Self::member(base, field)))))
619    }
620
621    /// The field, marked for [`Ada::read_call`]'s allocator, which names
622    /// the designated subtype.
623    fn reference_destination(
624        ast: &Ast,
625        id: Id<TypeNode>,
626        base: &str,
627        field: &str,
628    ) -> (Vec<Self::Stmt>, Self::Expr) {
629        let designated = crate::adapter::ada::reference_designated(ast, id).unwrap_or(id);
630        (
631            Vec::new(),
632            AExpr::ReferenceSlot {
633                place: inner(Self::member(base, field)),
634                mark: Path::single(&ada_type_name(ast, designated)),
635            },
636        )
637    }
638
639    /// `v.X := null;`
640    fn reference_absent(base: &str, field: &str) -> Vec<Self::Stmt> {
641        vec![ada::Stmt::Assign {
642            place: inner(Self::member(base, field)),
643            value: ada::Expr::Literal(Literal::Null),
644        }]
645    }
646
647    /// `v.X.Present` — reading a discriminant is always legal.
648    fn present(base: &str, field: &str) -> Self::Expr {
649        plain(Self::selected(
650            inner(Self::member(base, field)),
651            crate::adapter::ada::PRESENT,
652        ))
653    }
654
655    /// Nothing on the way in — the aggregate assignment in
656    /// [`Ada::read_call`] is what makes the member present — and
657    /// `(Present => False)` on the way out.
658    fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
659        if present {
660            return Vec::new();
661        }
662        vec![ada::Stmt::Assign {
663            place: inner(Self::member(base, field)),
664            value: ada::Expr::Aggregate(vec![ada::Association {
665                choice: Ident::new(crate::adapter::ada::PRESENT),
666                value: ada::Expr::Literal(Literal::Bool(false)),
667            }]),
668        }]
669    }
670
671    /// The marked place; the room is the aggregate [`Ada::read_call`]
672    /// assigns.
673    fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
674        (
675            Vec::new(),
676            AExpr::OptionalSlot(inner(Self::member(base, field))),
677        )
678    }
679
680    /// The operators as given; the renderer maps the spellings and
681    /// parenthesises. `=` is kept as a marked assignment so
682    /// [`Ada::perform`] can dispatch on the place.
683    fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
684        if op == "=" {
685            return AExpr::Assignment {
686                place: Box::new(lhs),
687                value: inner(rhs),
688            };
689        }
690        // Ada's modular types have no shift operators; `Interfaces`
691        // publishes the intrinsics (RM B.2), and the distance is a
692        // `Natural`.
693        if op == ">>" || op == "<<" {
694            return plain(Self::call_path(
695                Path::new(&[
696                    "Interfaces",
697                    if op == ">>" {
698                        "Shift_Right"
699                    } else {
700                        "Shift_Left"
701                    },
702                ]),
703                vec![inner(lhs), Self::to_natural(inner(rhs))],
704            ));
705        }
706        plain(ada::Expr::Binary {
707            op,
708            lhs: Box::new(inner(lhs)),
709            rhs: Box::new(inner(rhs)),
710        })
711    }
712
713    fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
714        plain(ada::Expr::Unary {
715            op,
716            value: Box::new(inner(value)),
717        })
718    }
719
720    /// Identity: the renderer parenthesises every compound operand, so
721    /// a bracket asked for is already there.
722    fn paren(value: Self::Expr) -> Self::Expr {
723        value
724    }
725
726    /// `Mark (v)` — with three rewrites. A `Boolean` target turns an
727    /// integer label into the literal; the walk's `signed char` — the C
728    /// spelling of a map key's signed order — goes through the
729    /// runtime's `As_Signed`, because an Ada conversion checks the
730    /// range C reinterprets; and an enumeration target converts through
731    /// `'Enum_Val`, because Ada's conversion does not cross the
732    /// enum/integer line (RM 4.6).
733    fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
734        if type_.mark == SIGNED_CHAR {
735            return plain(Self::call_path(
736                Self::runtime_path("As_Signed"),
737                vec![inner(value)],
738            ));
739        }
740        if type_.is_bool {
741            if let AExpr::Plain(ada::Expr::Literal(Literal::Int(text))) = &value {
742                return plain(ada::Expr::Literal(Literal::Bool(text != "0")));
743            }
744        }
745        if type_.is_enum {
746            return plain(ada::Expr::Attribute {
747                value: Box::new(ada::Expr::Name(mark_path(&type_.mark))),
748                name: Ident::new("Enum_Val"),
749                args: vec![inner(value)],
750            });
751        }
752        plain(ada::Expr::Convert {
753            mark: mark_path(&type_.mark),
754            value: Box::new(inner(value)),
755        })
756    }
757
758    /// The value, unchanged: Ada passes an `in` parameter as the value
759    /// it is, arrays included.
760    fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
761        value
762    }
763
764    /// A call to a generated codec or defaulter. `strcmp` is the
765    /// runtime's `Strcmp`, which spells C11 §7.24.4's unsigned-octet
766    /// order over the UTF-8 the members hold.
767    fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
768        let arguments: Vec<ada::Expr> = args.into_iter().map(inner).collect();
769        if callee == "strcmp" {
770            return plain(Self::call_path(Self::runtime_path("Strcmp"), arguments));
771        }
772        plain(Self::call_path(Path::single(callee), arguments))
773    }
774
775    /// `Zd_Wire.Write_U32 (w, x)` — the runtime is a package and the
776    /// receiver is its first parameter, `in out`.
777    fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
778        let mut arguments = vec![ada::Expr::name(receiver)];
779        arguments.extend(args.into_iter().map(inner));
780        plain(Self::call_path(
781            Self::runtime_path(&Self::operation(method)),
782            arguments,
783        ))
784    }
785
786    /// A free function of the runtime, qualified.
787    fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
788        plain(Self::call_path(
789            Self::runtime_path(&Self::operation(method)),
790            args.into_iter().map(inner).collect(),
791        ))
792    }
793
794    // -- statements ----------------------------------------------------
795
796    /// A local, under two Ada repairs. The walk's `zd_wire` holder
797    /// would hide the runtime package — RM 2.3(5/2) folds case, so
798    /// `zd_wire` and `Zd_Wire` are one identifier and RM 8.3 lets the
799    /// inner declaration win — and a counter the walk initialises with
800    /// a literal is held in `Natural`, because it is compared against
801    /// loop parameters, which Ada types `Integer` and refuses to
802    /// compare against a modular.
803    fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
804        let counts = type_.mark == "Interfaces.Unsigned_32"
805            && matches!(
806                &value,
807                Some(AExpr::Plain(ada::Expr::Literal(Literal::Int(_))))
808            );
809        ada::Stmt::Var(ada::VarDecl {
810            name: Ident::new(local_name(name)),
811            mark: mark_path(if counts { "Natural" } else { &type_.mark }),
812            constraint: None,
813            value: value.map(inner),
814        })
815    }
816
817    /// An assignment where the walk built one — routed by the place: an
818    /// `@optional` wrapper takes the engaged aggregate and a
819    /// discriminant the whole-record form — and a call statement
820    /// otherwise.
821    fn perform(value: Self::Expr) -> Self::Stmt {
822        match value {
823            AExpr::Assignment { place, value } => match *place {
824                AExpr::OptionalSlot(field) => ada::Stmt::Assign {
825                    place: field,
826                    value: Self::engaged(value),
827                },
828                AExpr::Discriminant(field) => Self::assign_discriminant(field, value),
829                other => ada::Stmt::Assign {
830                    place: inner(other),
831                    value,
832                },
833            },
834            other => ada::Stmt::Call(inner(other)),
835        }
836    }
837
838    /// `return v;`: an Ada decoder answers with the sample it built.
839    fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
840        ada::Stmt::Return(Some(match value {
841            Some(value) => inner(value),
842            None => ada::Expr::name("v"),
843        }))
844    }
845
846    fn when(
847        condition: Self::Expr,
848        then: Self::Block,
849        otherwise: Option<Self::Block>,
850    ) -> Self::Stmt {
851        ada::Stmt::If {
852            condition: inner(condition),
853            then,
854            otherwise: otherwise.filter(|body| !body.is_empty()),
855        }
856    }
857
858    /// `for zd_i in 0 .. Natural (bound) - 1 loop`: the loop parameter
859    /// is an integer, which is what an array index wants, and the bound
860    /// arrives as whatever unsigned the wire count is.
861    fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
862        ada::Stmt::For {
863            counter: Ident::new(local_name(counter)),
864            low: ada::Expr::int(0),
865            high: ada::Expr::Binary {
866                op: "-",
867                lhs: Box::new(Self::to_natural(inner(bound))),
868                rhs: Box::new(ada::Expr::int(1)),
869            },
870            body,
871        }
872    }
873
874    fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
875        ada::Stmt::While {
876            condition: inner(condition),
877            body,
878        }
879    }
880
881    fn leave() -> Self::Stmt {
882        ada::Stmt::Exit
883    }
884
885    fn note(doc: Doc) -> Self::Stmt {
886        ada::Stmt::Comment(doc)
887    }
888
889    /// Ada has no `(void)`: a function's answer cannot be left on the
890    /// floor, so the one value the walk drops — a collection's own
891    /// DHEADER — goes through the runtime's `Discard_U32`.
892    fn drop_value(value: Self::Expr) -> Self::Stmt {
893        ada::Stmt::Call(Self::call_path(
894            Self::runtime_path("Discard_U32"),
895            vec![inner(value)],
896        ))
897    }
898
899    /// Nothing: GNAT's default warning set does not flag an unread
900    /// parameter or local, and the probe is compiled without `-gnatwa`
901    /// on purpose — `docs` in `runtime/ada` carries the reason.
902    fn mark_used(_name: &str) -> Vec<Self::Stmt> {
903        Vec::new()
904    }
905
906    /// `place := zd_dec_T (r, behaviour);` — and through the engaged
907    /// aggregate or the whole-record form where the place is an
908    /// `@optional` member or the discriminant.
909    fn read_call(
910        callee: &str,
911        place: Self::Expr,
912        _form: Handoff,
913        behaviour: Self::Expr,
914    ) -> Self::Stmt {
915        let decoded = Self::call_path(
916            Path::single(callee),
917            vec![ada::Expr::name("r"), inner(behaviour)],
918        );
919        match place {
920            AExpr::OptionalSlot(field) => ada::Stmt::Assign {
921                place: field,
922                value: Self::engaged(decoded),
923            },
924            AExpr::Discriminant(field) => Self::assign_discriminant(field, decoded),
925            // `v.X := new T'(zd_dec_T (r, zd_tc));` — the allocation is
926            // what makes a member held by reference present.
927            AExpr::ReferenceSlot { place, mark } => ada::Stmt::Assign {
928                place,
929                value: ada::Expr::New {
930                    mark,
931                    init: ada::AllocInit::Qualified(Box::new(decoded)),
932                },
933            },
934            other => ada::Stmt::Assign {
935                place: inner(other),
936                value: decoded,
937            },
938        }
939    }
940
941    /// `v := zd_dec_T (r, zd_tc);` — a `typedef`, handed straight on:
942    /// the decoders of this column answer their value.
943    fn read_forward(callee: &str) -> Self::Stmt {
944        ada::Stmt::Assign {
945            place: ada::Expr::name("v"),
946            value: Self::call_path(
947                Path::single(callee),
948                vec![ada::Expr::name("r"), ada::Expr::name("zd_tc")],
949            ),
950        }
951    }
952
953    fn block(statements: Vec<Self::Stmt>) -> Self::Block {
954        statements
955    }
956
957    fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
958        block
959    }
960
961    /// `v := new Zd_Own_Array (0 .. Natural (n) - 1);`, then the fill:
962    /// the allocator's elements are default-valued storage. The array
963    /// type's name is not derivable from the element's, which is what
964    /// the subtype [`Ada::routine`] declares is for.
965    fn make_room(
966        base: &str,
967        _element: Self::Type,
968        count: Self::Expr,
969        _assigned_whole: bool,
970        fill: Vec<Self::Stmt>,
971    ) -> Vec<Self::Stmt> {
972        let mut out = vec![ada::Stmt::Assign {
973            place: ada::Expr::name(local_name(base)),
974            value: ada::Expr::New {
975                mark: Path::single(OWN_ARRAY),
976                init: ada::AllocInit::Range {
977                    low: Box::new(ada::Expr::int(0)),
978                    high: Box::new(ada::Expr::Binary {
979                        op: "-",
980                        lhs: Box::new(Self::to_natural(inner(count))),
981                        rhs: Box::new(ada::Expr::int(1)),
982                    }),
983                },
984            },
985        }];
986        out.extend(fill);
987        out
988    }
989
990    /// `v := new Zd_Own_Array'(v.all (0 .. Natural (count) - 1));` —
991    /// §7.3.1.2.1.12.3's TRIM: an Ada array cannot shrink in place, so
992    /// the kept prefix is a fresh allocation.
993    fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
994        vec![ada::Stmt::Assign {
995            place: ada::Expr::name(local_name(base)),
996            value: ada::Expr::New {
997                mark: Path::single(OWN_ARRAY),
998                init: ada::AllocInit::Qualified(Box::new(ada::Expr::Slice {
999                    value: Box::new(ada::Expr::Deref(Box::new(ada::Expr::name(local_name(
1000                        base,
1001                    ))))),
1002                    low: Box::new(ada::Expr::int(0)),
1003                    high: Box::new(ada::Expr::Binary {
1004                        op: "-",
1005                        lhs: Box::new(Self::to_natural(inner(count))),
1006                        rhs: Box::new(ada::Expr::int(1)),
1007                    }),
1008                })),
1009            },
1010        }]
1011    }
1012
1013    /// The subscript, with nothing around it: an Ada array element is a
1014    /// real place at every element type.
1015    fn sequence_element(
1016        base: &str,
1017        index: Self::Expr,
1018        _element: Self::Type,
1019    ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
1020        (
1021            Vec::new(),
1022            Self::index(Self::elements(base), index),
1023            Vec::new(),
1024        )
1025    }
1026
1027    // -- assembly ------------------------------------------------------
1028
1029    /// `procedure zd_enc_T (w : in out Zd_Wire.Writer; v : in T)` and
1030    /// `function zd_dec_T (r : in out Zd_Wire.Reader;
1031    /// zd_tc : in Zd_Wire.Try_Construct) return T`.
1032    ///
1033    /// A forward declaration is answered with the profile: Ada resolves
1034    /// nothing forward, and a mutually recursive pair needs both specs
1035    /// ahead of both bodies (RM 3.11).
1036    ///
1037    /// The declarative part is also where this column's two named
1038    /// subtypes land — `Zd_Sample` for the discriminant assignment and
1039    /// `Zd_Own_Array` for the collection allocators — and where the
1040    /// walk's `Interfaces.Integer_64 (v)` inside an enumeration's own
1041    /// encoder is rewritten through `Mark'Enum_Rep`, the one conversion
1042    /// Ada will not write directly.
1043    fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
1044        let mut params = vec![ada::Param {
1045            name: Ident::new(if spec.writing { "w" } else { "r" }),
1046            mode: ada::Mode::InOut,
1047            type_: Self::runtime_path(if spec.writing { "Writer" } else { "Reader" }),
1048        }];
1049        if spec.writing {
1050            params.push(ada::Param {
1051                name: Ident::new("v"),
1052                mode: ada::Mode::In,
1053                type_: mark_path(&spec.sample.mark),
1054            });
1055        }
1056        for (type_, name) in &spec.extra {
1057            params.push(ada::Param {
1058                name: Ident::new(local_name(name)),
1059                mode: ada::Mode::In,
1060                type_: mark_path(&type_.mark),
1061            });
1062        }
1063        let profile = ada::Subprogram {
1064            doc: spec.doc.clone(),
1065            kind: if spec.writing {
1066                ada::SubprogramKind::Procedure
1067            } else {
1068                ada::SubprogramKind::Function
1069            },
1070            name: Ident::new(&spec.name),
1071            params,
1072            result: if spec.writing {
1073                None
1074            } else {
1075                Some(mark_path(&spec.sample.mark))
1076            },
1077            is_abstract: false,
1078        };
1079        let Some(mut body) = spec.body else {
1080            return Some(ada::Decl::Subprogram(profile));
1081        };
1082
1083        if spec.writing {
1084            if spec.sample.is_enum {
1085                rewrite_enum_holder(&mut body, &spec.sample.mark);
1086            }
1087            return Some(ada::Decl::SubprogramBody(ada::SubprogramBody {
1088                spec: profile,
1089                body,
1090            }));
1091        }
1092
1093        // The sample starts as Table 9's value, through the generated
1094        // defaulter, so every position the stream does not carry is
1095        // already what the clause says it is.
1096        let mut statements = Vec::new();
1097        if spec.sample.is_union && names_mark(&body, OWN_SAMPLE) {
1098            statements.push(ada::Stmt::Subtype {
1099                name: Ident::new(OWN_SAMPLE),
1100                mark: mark_path(&spec.sample.mark),
1101            });
1102        }
1103        if let Some(array) = &spec.sample.own_array {
1104            if names_mark(&body, OWN_ARRAY) {
1105                statements.push(ada::Stmt::Subtype {
1106                    name: Ident::new(OWN_ARRAY),
1107                    mark: Path::single(array),
1108                });
1109            }
1110        }
1111        let defaulter = crate::default_ada::defaulter_from_decoder(&spec.name);
1112        statements.push(ada::Stmt::Var(ada::VarDecl {
1113            name: Ident::new("v"),
1114            mark: mark_path(&spec.sample.mark),
1115            constraint: None,
1116            value: Some(Self::call_path(Path::single(&defaulter), Vec::new())),
1117        }));
1118        statements.append(&mut body);
1119        statements.push(ada::Stmt::Return(Some(ada::Expr::name("v"))));
1120        Some(ada::Decl::SubprogramBody(ada::SubprogramBody {
1121            spec: profile,
1122            body: statements,
1123        }))
1124    }
1125
1126    /// A call to the generated defaulter, which is Table 9 once per
1127    /// type rather than once per position.
1128    ///
1129    /// # Errors
1130    /// Never; the signature matches the emitters around it.
1131    fn default_into(
1132        ast: &Ast,
1133        id: Id<TypeNode>,
1134        place: Self::Expr,
1135    ) -> Result<Self::Stmt, EncodeError> {
1136        let defaulted = Self::call_path(
1137            Path::single(&crate::default_ada::defaulter_name(ast, id)),
1138            Vec::new(),
1139        );
1140        Ok(match place {
1141            AExpr::OptionalSlot(field) => ada::Stmt::Assign {
1142                place: field,
1143                value: Self::engaged(defaulted),
1144            },
1145            AExpr::Discriminant(field) => Self::assign_discriminant(field, defaulted),
1146            other => ada::Stmt::Assign {
1147                place: inner(other),
1148                value: defaulted,
1149            },
1150        })
1151    }
1152
1153    /// # Errors
1154    /// As [`crate::default_ada::member_default`]: a `@default` this
1155    /// column cannot spell is refused rather than silently replaced by
1156    /// Table 9's zero.
1157    fn member_default(
1158        ast: &Ast,
1159        node: &MemberNode,
1160        type_: Id<TypeNode>,
1161        place: Self::Expr,
1162    ) -> Result<Vec<Self::Stmt>, EncodeError> {
1163        crate::default_ada::member_default(ast, node, type_, inner(place))
1164    }
1165
1166    /// The literal itself: an enumeration literal resolves by the type
1167    /// the context expects (RM 3.5.1), and the generated declarations
1168    /// share the probe's one declarative region.
1169    fn enumerator_constant(_ast: &Ast, _id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
1170        plain(ada::Expr::name(&crate::adapter::ada::component_name(
1171            enumerator,
1172        )))
1173    }
1174}
1175
1176/// A local's Ada spelling. One name moves: the walk's `zd_wire` holder
1177/// folds onto the runtime package `Zd_Wire` (RM 2.3(5/2)), and the
1178/// hidden package would take every runtime call in the enumeration
1179/// encoder down with it.
1180///
1181/// Every place the backend spells an identifier the *walk* chose goes
1182/// through here — the declaration, a mention, a loop parameter, a
1183/// subprogram's extra parameter, the base of a member, a subscript or
1184/// an allocator. One site left out is a declaration and a use that
1185/// disagree, which is not a compile error in a language whose package
1186/// name is in scope: the use resolves to the package instead.
1187fn local_name(text: &str) -> &str {
1188    if text.eq_ignore_ascii_case("zd_wire") {
1189        return "zd_onwire";
1190    }
1191    text
1192}
1193
1194/// Whether any expression in `body` names `mark` as a subtype — the
1195/// scan that decides whether [`Ada::routine`] declares it.
1196///
1197/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
1198/// The tree walked here is the one this generator emitted and owns, so
1199/// it holds no cycle; the depth is that body's height, and a defaulter
1200/// body is a handful of statements deep.
1201fn names_mark(body: &[ada::Stmt], mark: &str) -> bool {
1202    body.iter().any(|statement| stmt_names(statement, mark))
1203}
1204
1205/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
1206/// The tree walked here is the one this generator emitted and owns, so
1207/// it holds no cycle; the depth is that body's height, and a defaulter
1208/// body is a handful of statements deep.
1209fn stmt_names(statement: &ada::Stmt, mark: &str) -> bool {
1210    match statement {
1211        ada::Stmt::Null | ada::Stmt::Exit | ada::Stmt::Comment(_) => false,
1212        ada::Stmt::Var(var) => {
1213            path_is(&var.mark, mark)
1214                || var.constraint.as_ref().is_some_and(|e| expr_names(e, mark))
1215                || var.value.as_ref().is_some_and(|e| expr_names(e, mark))
1216        }
1217        ada::Stmt::Subtype { mark: target, .. } => path_is(target, mark),
1218        ada::Stmt::Assign { place, value } => expr_names(place, mark) || expr_names(value, mark),
1219        ada::Stmt::Call(value) => expr_names(value, mark),
1220        ada::Stmt::Return(value) => value.as_ref().is_some_and(|e| expr_names(e, mark)),
1221        ada::Stmt::If {
1222            condition,
1223            then,
1224            otherwise,
1225        } => {
1226            expr_names(condition, mark)
1227                || names_mark(then, mark)
1228                || otherwise
1229                    .as_ref()
1230                    .is_some_and(|body| names_mark(body, mark))
1231        }
1232        ada::Stmt::For {
1233            low, high, body, ..
1234        } => expr_names(low, mark) || expr_names(high, mark) || names_mark(body, mark),
1235        ada::Stmt::While { condition, body } => {
1236            expr_names(condition, mark) || names_mark(body, mark)
1237        }
1238        ada::Stmt::Declare { vars, body } => {
1239            vars.iter().any(|var| {
1240                path_is(&var.mark, mark)
1241                    || var.constraint.as_ref().is_some_and(|e| expr_names(e, mark))
1242                    || var.value.as_ref().is_some_and(|e| expr_names(e, mark))
1243            }) || names_mark(body, mark)
1244        }
1245    }
1246}
1247
1248fn path_is(path: &Path, mark: &str) -> bool {
1249    path.segments().len() == 1
1250        && path
1251            .segments()
1252            .first()
1253            .is_some_and(|segment| segment.as_str() == mark)
1254}
1255
1256/// zerodds-lint: recursion-depth 64 (expression nesting the walk builds).
1257/// The tree walked here is the one this generator emitted and owns, so
1258/// it holds no cycle; the depth is that expression's height, which
1259/// follows the member nesting the type walk already bounded.
1260fn expr_names(expr: &ada::Expr, mark: &str) -> bool {
1261    match expr {
1262        ada::Expr::Literal(_) | ada::Expr::BoxDefault => false,
1263        ada::Expr::Name(path) => path_is(path, mark),
1264        ada::Expr::Call { name, args } => {
1265            path_is(name, mark) || args.iter().any(|arg| expr_names(arg, mark))
1266        }
1267        ada::Expr::Negate(value) | ada::Expr::Unary { value, .. } | ada::Expr::Deref(value) => {
1268            expr_names(value, mark)
1269        }
1270        ada::Expr::Binary { lhs, rhs, .. } => expr_names(lhs, mark) || expr_names(rhs, mark),
1271        ada::Expr::Selected { value, .. } => expr_names(value, mark),
1272        ada::Expr::Indexed { value, index } => expr_names(value, mark) || expr_names(index, mark),
1273        ada::Expr::Slice { value, low, high } => {
1274            expr_names(value, mark) || expr_names(low, mark) || expr_names(high, mark)
1275        }
1276        ada::Expr::Attribute { value, args, .. } => {
1277            expr_names(value, mark) || args.iter().any(|arg| expr_names(arg, mark))
1278        }
1279        ada::Expr::Convert {
1280            mark: target,
1281            value,
1282        } => path_is(target, mark) || expr_names(value, mark),
1283        ada::Expr::Aggregate(parts) => parts.iter().any(|part| expr_names(&part.value, mark)),
1284        ada::Expr::New { mark: target, init } => {
1285            path_is(target, mark)
1286                || match init {
1287                    ada::AllocInit::Range { low, high } => {
1288                        expr_names(low, mark) || expr_names(high, mark)
1289                    }
1290                    ada::AllocInit::Qualified(value) => expr_names(value, mark),
1291                }
1292        }
1293    }
1294}
1295
1296/// Rewrite `Interfaces.Integer_64 (v)` into
1297/// `Interfaces.Integer_64 (Mark'Enum_Rep (v))` throughout an
1298/// enumeration's own encoder body: the walk reads the sample through
1299/// the target's `Int64` cast, and Ada's conversion does not take an
1300/// enumeration operand (RM 4.6). `'Enum_Rep` answers the code the
1301/// representation clause declared.
1302///
1303/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
1304/// The tree walked here is the one this generator emitted and owns, so
1305/// it holds no cycle; the depth is that body's height, and a defaulter
1306/// body is a handful of statements deep.
1307fn rewrite_enum_holder(body: &mut [ada::Stmt], mark: &str) {
1308    for statement in body.iter_mut() {
1309        rewrite_stmt(statement, mark);
1310    }
1311}
1312
1313/// zerodds-lint: recursion-depth 64 (statement nesting the walk builds).
1314/// The tree walked here is the one this generator emitted and owns, so
1315/// it holds no cycle; the depth is that body's height, and a defaulter
1316/// body is a handful of statements deep.
1317fn rewrite_stmt(statement: &mut ada::Stmt, mark: &str) {
1318    match statement {
1319        ada::Stmt::Null | ada::Stmt::Exit | ada::Stmt::Comment(_) | ada::Stmt::Subtype { .. } => {}
1320        ada::Stmt::Var(var) => {
1321            if let Some(value) = &mut var.value {
1322                rewrite_expr(value, mark);
1323            }
1324        }
1325        ada::Stmt::Assign { place, value } => {
1326            rewrite_expr(place, mark);
1327            rewrite_expr(value, mark);
1328        }
1329        ada::Stmt::Call(value) => rewrite_expr(value, mark),
1330        ada::Stmt::Return(value) => {
1331            if let Some(value) = value {
1332                rewrite_expr(value, mark);
1333            }
1334        }
1335        ada::Stmt::If {
1336            condition,
1337            then,
1338            otherwise,
1339        } => {
1340            rewrite_expr(condition, mark);
1341            rewrite_enum_holder(then, mark);
1342            if let Some(body) = otherwise {
1343                rewrite_enum_holder(body, mark);
1344            }
1345        }
1346        ada::Stmt::For {
1347            low, high, body, ..
1348        } => {
1349            rewrite_expr(low, mark);
1350            rewrite_expr(high, mark);
1351            rewrite_enum_holder(body, mark);
1352        }
1353        ada::Stmt::While { condition, body } => {
1354            rewrite_expr(condition, mark);
1355            rewrite_enum_holder(body, mark);
1356        }
1357        ada::Stmt::Declare { vars, body } => {
1358            for var in vars {
1359                if let Some(value) = &mut var.value {
1360                    rewrite_expr(value, mark);
1361                }
1362            }
1363            rewrite_enum_holder(body, mark);
1364        }
1365    }
1366}
1367
1368/// zerodds-lint: recursion-depth 64 (expression nesting the walk builds).
1369/// The tree walked here is the one this generator emitted and owns, so
1370/// it holds no cycle; the depth is that expression's height, which
1371/// follows the member nesting the type walk already bounded.
1372fn rewrite_expr(expr: &mut ada::Expr, mark: &str) {
1373    if let ada::Expr::Convert {
1374        mark: target,
1375        value,
1376    } = expr
1377    {
1378        let is_holder = target
1379            .segments()
1380            .last()
1381            .is_some_and(|segment| segment.as_str() == "Integer_64");
1382        let is_sample = matches!(value.as_ref(), ada::Expr::Name(path)
1383            if path.segments().len() == 1
1384                && path.segments().first().is_some_and(|s| s.as_str() == "v"));
1385        if is_holder && is_sample {
1386            let sample = core::mem::replace(value.as_mut(), ada::Expr::BoxDefault);
1387            **value = ada::Expr::Attribute {
1388                value: Box::new(ada::Expr::Name(mark_path(mark))),
1389                name: Ident::new("Enum_Rep"),
1390                args: vec![sample],
1391            };
1392            return;
1393        }
1394    }
1395    match expr {
1396        ada::Expr::Literal(_) | ada::Expr::Name(_) | ada::Expr::BoxDefault => {}
1397        ada::Expr::Call { args, .. } => {
1398            for arg in args {
1399                rewrite_expr(arg, mark);
1400            }
1401        }
1402        ada::Expr::Negate(value) | ada::Expr::Unary { value, .. } | ada::Expr::Deref(value) => {
1403            rewrite_expr(value, mark);
1404        }
1405        ada::Expr::Binary { lhs, rhs, .. } => {
1406            rewrite_expr(lhs, mark);
1407            rewrite_expr(rhs, mark);
1408        }
1409        ada::Expr::Selected { value, .. } => rewrite_expr(value, mark),
1410        ada::Expr::Indexed { value, index } => {
1411            rewrite_expr(value, mark);
1412            rewrite_expr(index, mark);
1413        }
1414        ada::Expr::Slice { value, low, high } => {
1415            rewrite_expr(value, mark);
1416            rewrite_expr(low, mark);
1417            rewrite_expr(high, mark);
1418        }
1419        ada::Expr::Attribute { value, args, .. } => {
1420            rewrite_expr(value, mark);
1421            for arg in args {
1422                rewrite_expr(arg, mark);
1423            }
1424        }
1425        ada::Expr::Convert { value, .. } => rewrite_expr(value, mark),
1426        ada::Expr::Aggregate(parts) => {
1427            for part in parts {
1428                rewrite_expr(&mut part.value, mark);
1429            }
1430        }
1431        ada::Expr::New { init, .. } => match init {
1432            ada::AllocInit::Range { low, high } => {
1433                rewrite_expr(low, mark);
1434                rewrite_expr(high, mark);
1435            }
1436            ada::AllocInit::Qualified(value) => rewrite_expr(value, mark),
1437        },
1438    }
1439}