Skip to main content

zerodds_lang_rules/emit/
ada83.rs

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