Skip to main content

zerodds_lang_rules/emit/
c.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! The C backend of [`CodecTarget`].
4//!
5//! Every method here builds what [`crate::encode_c`] built by hand
6//! before the walk was made generic, node for node. That is the
7//! contract this backend is held to: the C column's octets are recorded
8//! over 17 220 slots, and a backend that produced tidier C would be
9//! reporting different bytes for the same layout key.
10//!
11//! ## What C makes visible here
12//!
13//! - **A pointer parameter.** A codec takes `const T *v` and reaches its
14//!   members with `->`. [`CodecTarget::member`] is that arrow, and the
15//!   targets that hand a reference spell the same call `.`.
16//! - **An array parameter decays.** C adjusts an array parameter to a
17//!   pointer to its first element, so a call site passes `v->grid` for
18//!   an array and `&v->x` for everything else — [`Handoff`].
19//! - **One namespace.** `runtime/c` exports `zdw_*` for both directions;
20//!   the reader's functions are `zdw_read_*` and not `zdr_*`. The
21//!   receiver local is what tells them apart at a call site.
22
23use super::{BitPatternSource, CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
24use crate::adapter::c::{c_codec_tag, c_holder, c_map_entry_name, c_value_type, module_prefix};
25use crate::encode::EncodeError;
26use zerodds_ast::{Ast, MemberNode, TypeNode};
27use zerodds_ast_core::Id;
28use zerodds_lang_cst::c;
29use zerodds_lang_cst::common::{Doc, Ident, Literal};
30
31/// The C backend.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct C;
34
35impl C {
36    /// The runtime's prefix, which both directions share.
37    const PREFIX: &'static str = "zdw_";
38}
39
40impl CodecTarget for C {
41    type Expr = c::Expr;
42    type Stmt = c::Statement;
43    type Block = c::Block;
44    type Type = c::Type;
45    type Decl = c::Decl;
46
47    // -- types --------------------------------------------------------
48
49    fn scalar(kind: Scalar) -> Self::Type {
50        c::Type::Primitive(match kind {
51            Scalar::Index => c::Primitive::UInt32,
52            Scalar::Size => c::Primitive::Size,
53            Scalar::Bool => c::Primitive::Bool,
54            Scalar::Char => c::Primitive::Char,
55            Scalar::Int8 => c::Primitive::Int8,
56            Scalar::Int16 => c::Primitive::Int16,
57            Scalar::Int32 => c::Primitive::Int32,
58            Scalar::Int64 => c::Primitive::Int64,
59            Scalar::UInt8 => c::Primitive::UInt8,
60            Scalar::UInt16 => c::Primitive::UInt16,
61            Scalar::UInt32 => c::Primitive::UInt32,
62            Scalar::UInt64 => c::Primitive::UInt64,
63            Scalar::Float => c::Primitive::Float,
64            Scalar::Double => c::Primitive::Double,
65            Scalar::LongDouble => c::Primitive::LongDouble,
66        })
67    }
68
69    fn named(name: &str) -> Self::Type {
70        c::Type::Named(Ident::new(name))
71    }
72
73    fn nothing() -> Self::Type {
74        c::Type::Void
75    }
76
77    /// The runtime's own small structs, in its single namespace.
78    fn handle(kind: Handle) -> Self::Type {
79        c::Type::Named(Ident::new(match kind {
80            Handle::DHeader => "zdw_dheader",
81            Handle::EmHeader => "zdw_emheader",
82            Handle::Pid => "zdw_pid",
83            Handle::TryConstruct => "zdw_try_construct",
84            Handle::ReaderMark => "zdw_reader_mark",
85            Handle::PidRead => "zdw_pid_read",
86            Handle::EmHeaderRead => "zdw_emheader_read",
87        }))
88    }
89
90    fn unsigned_holder(bits: u16) -> Self::Type {
91        c::Type::Primitive(c_holder(bits))
92    }
93
94    /// `(uint32_t)(uint8_t)v` for a `char` and `(uint32_t)v` for a
95    /// `wchar`, which is what the recorded octets were produced with.
96    fn code_point(value: Self::Expr, narrow: bool) -> Self::Expr {
97        let inner = if narrow {
98            c::Expr::Cast {
99                type_: Box::new(c::Type::Primitive(c::Primitive::UInt8)),
100                value: Box::new(value),
101            }
102        } else {
103            value
104        };
105        c::Expr::Cast {
106            type_: Box::new(c::Type::Primitive(c::Primitive::UInt32)),
107            value: Box::new(inner),
108        }
109    }
110
111    /// `runtime/c` publishes these as macros in one namespace.
112    fn constant(which: Constant) -> Self::Expr {
113        c::Expr::Name(Ident::new(match which {
114            Constant::WcharWidth2 => "ZDW_WCHAR_WIDTH_2",
115            Constant::WcharWidth4 => "ZDW_WCHAR_WIDTH_4",
116            Constant::WcharFixed => "ZDW_WCHAR_FIXED",
117            Constant::WcharGiopLengthPrefixed => "ZDW_WCHAR_GIOP_LENGTH_PREFIXED",
118            Constant::WstringUnitsStream => "ZDW_WSTRING_UNITS_STREAM",
119            Constant::WstringUnitsBigEndianAlways => "ZDW_WSTRING_UNITS_BIG_ENDIAN_ALWAYS",
120            Constant::WstringUnitsByteOrderMark => "ZDW_WSTRING_UNITS_BYTE_ORDER_MARK",
121            Constant::WstringUnitsHostUnswapped => "ZDW_WSTRING_UNITS_HOST_UNSWAPPED",
122            Constant::WstringSpecification => "ZDW_WSTRING_SPECIFICATION",
123            Constant::WstringCharacterCount => "ZDW_WSTRING_CHARACTER_COUNT",
124            Constant::WstringCharacterCountTerminated => "ZDW_WSTRING_CHARACTER_COUNT_TERMINATED",
125            Constant::WstringSpecificationWideUnits => "ZDW_WSTRING_SPECIFICATION_WIDE_UNITS",
126            Constant::WstringRosTypeSupport => "ZDW_WSTRING_ROS_TYPE_SUPPORT",
127            Constant::WstringOctetsTerminated2 => "ZDW_WSTRING_OCTETS_TERMINATED_2",
128            Constant::WstringOctetsTerminated4 => "ZDW_WSTRING_OCTETS_TERMINATED_4",
129            Constant::WstringCharactersTerminated2 => "ZDW_WSTRING_CHARACTERS_TERMINATED_2",
130            Constant::FixedDeclared => "ZDW_FIXED_DECLARED",
131            Constant::FixedValuePrecision => "ZDW_FIXED_VALUE_PRECISION",
132            Constant::FixedValueStringDigits => "ZDW_FIXED_VALUE_STRING_DIGITS",
133            Constant::ParameterMember => "ZDW_PARAM_MEMBER",
134            Constant::ParameterOptional => "ZDW_PARAM_OPTIONAL",
135            Constant::TryConstructDiscard => "ZDW_TC_DISCARD",
136            Constant::TryConstructTrim => "ZDW_TC_TRIM",
137            Constant::TryConstructUseDefault => "ZDW_TC_USE_DEFAULT",
138            Constant::StatusOk => "ZDW_OK",
139            Constant::StatusDiscarded => "ZDW_DISCARDED",
140            Constant::StatusImpossibleLength => "ZDW_IMPOSSIBLE_LENGTH",
141            Constant::StatusOutOfRange => "ZDW_OUT_OF_RANGE",
142            Constant::StatusTruncated => "ZDW_TRUNCATED",
143        }))
144    }
145
146    /// The walk's `Int64` cast turned around: `zdw_bit_pattern_int`
147    /// takes `uint64_t` (the pattern is storage bits, not a number),
148    /// and handing it the walk's `(int64_t)` is the implicit
149    /// signedness conversion `-Wsign-conversion -Werror` refuses —
150    /// `combined-everything`'s suppressed union arm stopped compiling.
151    /// The low octets the overlay writes are the same either way; only
152    /// the parameter's spelling changes. Every other column's runtime
153    /// takes the pattern signed (`long` in Java and C#) and keeps the
154    /// default.
155    fn bit_pattern(source: BitPatternSource, value: Self::Expr) -> Self::Expr {
156        let value = match (&source, value) {
157            (BitPatternSource::Int { .. }, c::Expr::Cast { value, .. }) => c::Expr::Cast {
158                type_: Box::new(c::Type::Primitive(c::Primitive::UInt64)),
159                value,
160            },
161            (_, other) => other,
162        };
163        match source {
164            BitPatternSource::Int { octets } => Self::runtime_static(
165                "bit_pattern_int",
166                vec![value, Self::int(i128::from(octets))],
167            ),
168            BitPatternSource::Bool => Self::runtime_static("bit_pattern_bool", vec![value]),
169            BitPatternSource::Float32 => Self::runtime_static("bit_pattern_f32", vec![value]),
170            BitPatternSource::Float64 => Self::runtime_static("bit_pattern_f64", vec![value]),
171        }
172    }
173
174    /// C has free functions, so this is [`CodecTarget::call`] with the
175    /// runtime's prefix and no receiver.
176    fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
177        c::Expr::Call {
178            callee: Box::new(c::Expr::Name(Ident::new(&format!(
179                "{}{method}",
180                Self::PREFIX
181            )))),
182            args,
183        }
184    }
185
186    // -- expressions --------------------------------------------------
187
188    fn name(text: &str) -> Self::Expr {
189        c::Expr::Name(Ident::new(text))
190    }
191
192    fn int(value: i128) -> Self::Expr {
193        c::Expr::Literal(Literal::int(value))
194    }
195
196    fn literal(value: Literal) -> Self::Expr {
197        c::Expr::Literal(value)
198    }
199
200    fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
201        c::Expr::Call {
202            callee: Box::new(c::Expr::Name(Ident::new(callee))),
203            args,
204        }
205    }
206
207    fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
208        let mut all = vec![c::Expr::Name(Ident::new(receiver))];
209        all.extend(args);
210        c::Expr::Call {
211            callee: Box::new(c::Expr::Name(Ident::new(&format!(
212                "{}{method}",
213                Self::PREFIX
214            )))),
215            args: all,
216        }
217    }
218
219    /// `runtime/c` carries the three HDDS axes: `zdw_begin_packed_element`
220    /// and `zdw_finish_packed_element`, the DHEADER pair for a sequence
221    /// element's own length, and `zdw_write_wstring_utf8_octets` /
222    /// `zdw_read_wstring_utf8_octets`.
223    fn carries_hdds_axes() -> bool {
224        true
225    }
226
227    fn member(base: &str, field: &str) -> Self::Expr {
228        c::Expr::Arrow {
229            value: Box::new(c::Expr::Name(Ident::new(base))),
230            name: Ident::new(field),
231        }
232    }
233
234    fn whole(base: &str) -> Self::Expr {
235        c::Expr::Deref(Box::new(c::Expr::Name(Ident::new(base))))
236    }
237
238    /// The suffix says which type the constant is; the cast at the use
239    /// site says which type it is compared against.
240    ///
241    /// −2^63 has no such constant to suffix:
242    /// `INT64_C(-9223372036854775808)` pastes the suffix onto digits C
243    /// has no signed type for, and the minus then applies to an
244    /// unsigned constant.
245    /// [`zerodds_lang_render::c::integer_text`] carries the clause and
246    /// the measurement, and answers with the subtraction `<stdint.h>`
247    /// spells `INT64_MIN` as. A parenthesised expression and no longer
248    /// a literal, which every site this reaches admits: the walk puts
249    /// it inside a cast in an `if` condition and never where C requires
250    /// a token — there is no `case` label in the generated C, the union
251    /// reader is a chain of comparisons.
252    fn wide_int(value: i128) -> Self::Expr {
253        let text = if value > i128::from(i64::MAX) {
254            format!("UINT64_C({value})")
255        } else if value == i128::from(i64::MIN) {
256            zerodds_lang_render::c::integer_text(value)
257        } else if value > i128::from(i32::MAX) || value < i128::from(i32::MIN) {
258            format!("INT64_C({value})")
259        } else {
260            format!("{value}")
261        };
262        c::Expr::Literal(Literal::Int(text))
263    }
264
265    fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
266        c::Expr::Member {
267            value: Box::new(value),
268            name: Ident::new(field),
269        }
270    }
271
272    fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
273        c::Expr::Index {
274            value: Box::new(value),
275            index: Box::new(at),
276        }
277    }
278
279    /// A C sequence keeps its length in the struct's `_length`; an
280    /// array's bound is not kept anywhere, because the type carries it.
281    fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
282        match fixed {
283            Some(bound) => c::Expr::Literal(Literal::int(i128::from(bound))),
284            None => Self::member(base, "_length"),
285        }
286    }
287
288    fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
289        c::Expr::Binary {
290            op,
291            lhs: Box::new(lhs),
292            rhs: Box::new(rhs),
293        }
294    }
295
296    fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
297        c::Expr::Unary {
298            op,
299            value: Box::new(value),
300        }
301    }
302
303    fn paren(value: Self::Expr) -> Self::Expr {
304        c::Expr::Paren(Box::new(value))
305    }
306
307    fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
308        c::Expr::Cast {
309            type_: Box::new(type_),
310            value: Box::new(value),
311        }
312    }
313
314    /// `&x` for a value and the array itself for an array — except
315    /// where the value is already a dereference.
316    ///
317    /// `&*p` and `p` are the same pointer (C11 §6.5.3.2p3 says the two
318    /// cancel and that neither is evaluated), and a member held behind
319    /// an indirection reaches its value through
320    /// [`C::behind_indirection`], so every call site that hands one to a
321    /// codec would spell it `&*v->next`. The pair is dropped here rather
322    /// than at the seam because the seam answers with the *value* — the
323    /// encoder's `zdw_write_*` and the `sizeof` beside it both need it —
324    /// and only the hand-off wants the address back.
325    fn handoff(value: Self::Expr, form: Handoff) -> Self::Expr {
326        match (form, value) {
327            (Handoff::Array, value) => value,
328            (Handoff::Value, c::Expr::Deref(inner)) => *inner,
329            (Handoff::Value, value) => c::Expr::AddressOf(Box::new(value)),
330        }
331    }
332
333    // -- statements ---------------------------------------------------
334
335    fn declare(type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
336        c::Statement::Declare {
337            type_,
338            name: Ident::new(name),
339            value,
340        }
341    }
342
343    fn perform(value: Self::Expr) -> Self::Stmt {
344        c::Statement::Expr(value)
345    }
346
347    fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
348        c::Statement::Return(value)
349    }
350
351    fn when(
352        condition: Self::Expr,
353        then: Self::Block,
354        otherwise: Option<Self::Block>,
355    ) -> Self::Stmt {
356        c::Statement::If {
357            condition,
358            then,
359            otherwise,
360        }
361    }
362
363    /// `for (uint32_t i = 0; i < bound; i = i + 1)`, the counter
364    /// declared in the initialiser.
365    ///
366    /// Two things here are older than this backend and are kept because
367    /// changing either moves the generated text without moving a byte:
368    /// the counter is `uint32_t` rather than `size_t`, which is the type
369    /// a sequence length has on the wire and keeps `-Wsign-conversion`
370    /// quiet where it is compared against one; and the step is written
371    /// out rather than `i++`, which the tree has no node for.
372    fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
373        c::Statement::For {
374            init: Box::new(c::Statement::Declare {
375                type_: c::Type::Primitive(c::Primitive::UInt32),
376                name: Ident::new(counter),
377                value: Some(c::Expr::Literal(Literal::int(0))),
378            }),
379            condition: c::Expr::Binary {
380                op: "<",
381                lhs: Box::new(c::Expr::Name(Ident::new(counter))),
382                rhs: Box::new(bound),
383            },
384            step: c::Expr::Binary {
385                op: "=",
386                lhs: Box::new(c::Expr::Name(Ident::new(counter))),
387                rhs: Box::new(c::Expr::Binary {
388                    op: "+",
389                    lhs: Box::new(c::Expr::Name(Ident::new(counter))),
390                    rhs: Box::new(c::Expr::Literal(Literal::int(1))),
391                }),
392            },
393            body,
394        }
395    }
396
397    fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
398        c::Statement::While { condition, body }
399    }
400
401    fn leave() -> Self::Stmt {
402        c::Statement::Break
403    }
404
405    fn note(doc: Doc) -> Self::Stmt {
406        c::Statement::Comment(doc)
407    }
408
409    // -- assembly -----------------------------------------------------
410
411    /// The five statements C needs, with the fill inside the guard.
412    ///
413    /// `zdw_reader_alloc` takes from a buffer the caller set, so a
414    /// failure is an exhausted arena rather than an exhausted machine —
415    /// which is why it returns nothing rather than aborting, and why
416    /// the generated reader has to check.
417    fn make_room(
418        base: &str,
419        element: Self::Type,
420        count: Self::Expr,
421        // C's slot is storage the moment the buffer is there, so
422        // whether the fill assigns it or writes into it makes no
423        // difference to what has to be allocated.
424        _assigned_whole: bool,
425        fill: Vec<Self::Stmt>,
426    ) -> Vec<Self::Stmt> {
427        let field = |name: &str| Self::member(base, name);
428        let assign = |place: c::Expr, value: c::Expr| {
429            c::Statement::Expr(c::Expr::Binary {
430                op: "=",
431                lhs: Box::new(place),
432                rhs: Box::new(value),
433            })
434        };
435        let mut guarded = vec![
436            assign(
437                field("_buffer"),
438                c::Expr::Cast {
439                    type_: Box::new(c::Type::Pointer(Box::new(element.clone()))),
440                    value: Box::new(Self::runtime(
441                        "r",
442                        "reader_alloc",
443                        vec![c::Expr::Binary {
444                            op: "*",
445                            lhs: Box::new(c::Expr::Cast {
446                                type_: Box::new(c::Type::Primitive(c::Primitive::Size)),
447                                value: Box::new(count.clone()),
448                            }),
449                            rhs: Box::new(c::Expr::SizeOf(Box::new(element))),
450                        }],
451                    )),
452                },
453            ),
454            c::Statement::If {
455                condition: c::Expr::Binary {
456                    op: "==",
457                    lhs: Box::new(field("_buffer")),
458                    rhs: Box::new(Self::absent()),
459                },
460                then: c::Block {
461                    statements: vec![c::Statement::Return(None)],
462                },
463                otherwise: None,
464            },
465        ];
466        guarded.extend(fill);
467        // The capacity is the count, and the arena the buffer is in is not the sequence's to free.
468        vec![
469            assign(field("_maximum"), count.clone()),
470            assign(field("_release"), c::Expr::Literal(Literal::Bool(false))),
471            assign(field("_length"), count.clone()),
472            assign(field("_buffer"), Self::absent()),
473            c::Statement::If {
474                condition: c::Expr::Binary {
475                    op: ">",
476                    lhs: Box::new(count),
477                    rhs: Box::new(c::Expr::Literal(Literal::Int("0u".to_string()))),
478                },
479                then: c::Block {
480                    statements: guarded,
481                },
482                otherwise: None,
483            },
484        ]
485    }
486
487    /// `v->x != NULL`. XTypes 1.3 §7.5.1.2.4.1: "A NULL pointer shall
488    /// indicate an omitted value."
489    fn present(base: &str, field: &str) -> Self::Expr {
490        c::Expr::Binary {
491            op: "!=",
492            lhs: Box::new(Self::member(base, field)),
493            rhs: Box::new(Self::absent()),
494        }
495    }
496
497    /// XTypes 1.3 §7.5.1.4.1.3: C binds a derived structure with the base as its first member
498    /// `parent`, so a member of the base is `parent.m` and one of its base `parent.parent.m`.
499    fn inherited_path(depth: usize) -> Option<String> {
500        Some(format!("{}.", crate::adapter::c::PARENT).repeat(depth))
501    }
502
503    /// Nothing on the way in — [`C::make_indirection`] allocates, and a
504    /// `string` is assigned by its own read — and `v->x = NULL;` on the
505    /// way out.
506    fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
507        if present {
508            return Vec::new();
509        }
510        vec![c::Statement::Expr(c::Expr::Binary {
511            op: "=",
512            lhs: Box::new(Self::member(base, field)),
513            rhs: Box::new(Self::absent()),
514        })]
515    }
516
517    /// An `@optional` member is a pointer, `T *`, and a `string` is the
518    /// `const char *` it already is.
519    fn optionals_are_pointers() -> bool {
520        true
521    }
522
523    /// `if (!(v->x != NULL)) { zdw_writer_refuse(w, ZDW_UNSET_EXTERNAL); return; }`
524    fn refuse_unset_external(present: Self::Expr) -> Vec<Self::Stmt> {
525        vec![c::Statement::If {
526            condition: c::Expr::Unary {
527                op: "!",
528                value: Box::new(c::Expr::Paren(Box::new(present))),
529            },
530            then: c::Block {
531                statements: vec![
532                    c::Statement::Expr(Self::runtime(
533                        "w",
534                        "writer_refuse",
535                        vec![c::Expr::Name(Ident::new("ZDW_UNSET_EXTERNAL"))],
536                    )),
537                    c::Statement::Return(None),
538                ],
539            },
540            otherwise: None,
541        }]
542    }
543
544    fn case_member(base: &str, field: &str) -> Self::Expr {
545        c::Expr::Member {
546            value: Box::new(Self::member(base, "_u")),
547            name: Ident::new(field),
548        }
549    }
550
551    fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
552        c::Expr::Member {
553            value: Box::new(entry),
554            name: Ident::new(if key { "key" } else { "value" }),
555        }
556    }
557
558    fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
559        Self::member(base, field)
560    }
561
562    /// `v->_buffer[i]`, and nothing around it. Every C object is
563    /// addressable, so there is no proxy to work around.
564    fn sequence_element(
565        base: &str,
566        index: Self::Expr,
567        _element: Self::Type,
568    ) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
569        (
570            Vec::new(),
571            Self::index(Self::elements(base), index),
572            Vec::new(),
573        )
574    }
575
576    /// `v->_length = count;`. The elements past it stay in the buffer,
577    /// unreferenced, and the length is what says the sequence stops.
578    fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
579        vec![c::Statement::Expr(c::Expr::Binary {
580            op: "=",
581            lhs: Box::new(Self::count_of(base, None)),
582            rhs: Box::new(count),
583        })]
584    }
585
586    /// The member, and nothing to do first: for a `string` the member is
587    /// the `const char *` the read assigns. Every other optional member is
588    /// a pointer (XTypes 1.3 §7.5.1.2.4.1) and [`C::make_indirection`] is
589    /// where it is made to point somewhere before it is written through.
590    fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
591        (Vec::new(), Self::member(base, field))
592    }
593
594    /// `*v->next` — the member dereferenced.
595    ///
596    /// `crates/lang-rules/src/adapter/c.rs` declares a member that
597    /// closes a cycle as `T *`, because a C structure holds its members
598    /// as storage and `T next;` inside `T` is a structure that contains
599    /// itself: `field 'next' has incomplete type`. The pointer is what
600    /// gives the type a size, and `@external` is the annotation the case
601    /// carries for exactly that reason — XTypes 1.3 Table 21, "stored
602    /// externally to the containing type". No octet moves: the codec
603    /// takes the value either way, and this is where the value is.
604    fn externals_are_pointers() -> bool {
605        true
606    }
607
608    fn behind_indirection(_ast: &Ast, _id: Id<TypeNode>, place: Self::Expr) -> Self::Expr {
609        c::Expr::Deref(Box::new(place))
610    }
611
612    /// The same place, with the pointer made to point somewhere first.
613    ///
614    /// ```c
615    /// v->next = (m_Node *)zdw_reader_alloc(r, sizeof(m_Node));
616    /// if (v->next == NULL) { return; }
617    /// ```
618    ///
619    /// The allocation is [`CodecTarget::make_room`]'s, out of the buffer
620    /// the caller handed the reader: a failure is an exhausted arena and
621    /// not an exhausted machine, so it is a value that is not there and
622    /// the generated reader checks. Nothing frees it, for the reason
623    /// nothing frees a sequence's buffer — the arena is the caller's and
624    /// outlives the read.
625    fn make_indirection(
626        ast: &Ast,
627        id: Id<TypeNode>,
628        place: Self::Expr,
629    ) -> (Vec<Self::Stmt>, Self::Expr) {
630        let held = Self::value_type(ast, id);
631        let made = c::Statement::Expr(c::Expr::Binary {
632            op: "=",
633            lhs: Box::new(place.clone()),
634            rhs: Box::new(c::Expr::Cast {
635                type_: Box::new(c::Type::Pointer(Box::new(held.clone()))),
636                value: Box::new(Self::runtime(
637                    "r",
638                    "reader_alloc",
639                    vec![c::Expr::SizeOf(Box::new(held))],
640                )),
641            }),
642        });
643        let guard = c::Statement::If {
644            condition: c::Expr::Binary {
645                op: "==",
646                lhs: Box::new(place.clone()),
647                rhs: Box::new(Self::absent()),
648            },
649            then: c::Block {
650                statements: vec![c::Statement::Return(None)],
651            },
652            otherwise: None,
653        };
654        (vec![made, guard], c::Expr::Deref(Box::new(place)))
655    }
656
657    fn elements(base: &str) -> Self::Expr {
658        Self::member(base, "_buffer")
659    }
660
661    fn absent() -> Self::Expr {
662        c::Expr::Name(Ident::new("NULL"))
663    }
664
665    /// `v != NULL`. The reader allocates out of a buffer the caller
666    /// set, and a buffer that runs out is a value that is not there.
667    fn materialised(value: Self::Expr) -> Option<Self::Expr> {
668        Some(c::Expr::Binary {
669            op: "!=",
670            lhs: Box::new(value),
671            rhs: Box::new(Self::absent()),
672        })
673    }
674
675    fn block(statements: Vec<Self::Stmt>) -> Self::Block {
676        c::Block { statements }
677    }
678
679    fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
680        block.statements
681    }
682
683    /// `static void zd_enc_T(zdw_writer *w, const T *v)`.
684    ///
685    /// `static` because a probe's translation unit is one file and an
686    /// external codec nothing else declares is a warning under the flags
687    /// the probe compiles with.
688    fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
689        let stream = c::Param {
690            type_: c::Type::Pointer(Box::new(c::Type::Named(Ident::new(if spec.writing {
691                "zdw_writer"
692            } else {
693                "zdw_reader"
694            })))),
695            name: Some(Ident::new(if spec.writing { "w" } else { "r" })),
696        };
697        // The written direction may not touch the sample and the read
698        // direction must; `const` is what says so, and dropping it would
699        // let a walk assign through a parameter it was handed to read.
700        let constant = |type_: c::Type| {
701            if spec.writing {
702                c::Type::Const(Box::new(type_))
703            } else {
704                type_
705            }
706        };
707        // An array parameter is adjusted to a pointer to its first
708        // element on its own, so it is spelled as the array and indexed
709        // directly; `(*v)[i][j]` would otherwise need parentheses the
710        // tree has no node for.
711        let value = match spec.held {
712            Handoff::Array => constant(spec.sample),
713            Handoff::Value => c::Type::Pointer(Box::new(constant(spec.sample))),
714        };
715        let mut params = vec![
716            stream,
717            c::Param {
718                type_: value,
719                name: Some(Ident::new("v")),
720            },
721        ];
722        params.extend(spec.extra.into_iter().map(|(type_, name)| c::Param {
723            type_,
724            name: Some(Ident::new(&name)),
725        }));
726        Some(c::Decl::Function(c::Function {
727            doc: spec.doc,
728            is_static: true,
729            is_inline: false,
730            is_maybe_unused: spec.tolerate_unused,
731            returns: c::Type::Void,
732            name: Ident::new(&spec.name),
733            params,
734            body: spec.body,
735        }))
736    }
737
738    // -- the materialiser's boundary ----------------------------------
739
740    fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
741        c_value_type(ast, id)
742    }
743
744    /// [`c_codec_tag`] and not [`crate::adapter::c::c_type_name`]: this names the routine a
745    /// type's codec is emitted under, and the two part on a string. A
746    /// `string<4>` and a `string` are both a `zd_string` — one C type,
747    /// no declaration of its own — so the tag is the only place their
748    /// bound can keep their routines apart. Everywhere the answer is
749    /// used as a *spelling* it is an aggregate's, where the two
750    /// functions agree.
751    fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
752        c_codec_tag(ast, id)
753    }
754
755    fn escape(name: &str) -> String {
756        c::escape_keyword(name)
757    }
758
759    fn map_entry_name(ast: &Ast, id: Id<TypeNode>) -> String {
760        c_map_entry_name(ast, id)
761    }
762
763    /// The enclosing modules flattened onto the bare enumerator, which
764    /// is where a C enumeration constant lives — C11 §6.7.2.2 puts it
765    /// in the scope enclosing the enumeration and not in the
766    /// enumeration.
767    fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, name: &str) -> Self::Expr {
768        Self::name(&enumerator_constant_name(ast, id, name))
769    }
770
771    fn module_prefix(ast: &Ast, id: Id<TypeNode>) -> String {
772        module_prefix(ast, id)
773    }
774
775    /// Never refuses: `default_c` emits a defaulter for every type the
776    /// C materialiser can declare, and a type it cannot declare has
777    /// already been refused before a decoder is asked for.
778    fn default_into(
779        ast: &Ast,
780        id: Id<TypeNode>,
781        place: Self::Expr,
782    ) -> Result<Self::Stmt, EncodeError> {
783        Ok(crate::default_c::default_into(ast, id, place))
784    }
785
786    fn member_default(
787        ast: &Ast,
788        node: &MemberNode,
789        type_: Id<TypeNode>,
790        place: Self::Expr,
791    ) -> Result<Vec<Self::Stmt>, EncodeError> {
792        crate::default_c::member_default(ast, node, type_, place)
793    }
794}
795
796/// The name a C codec function takes, given the runtime's prefix.
797///
798/// Exposed because a caller that emits a prototype and a body has to
799/// agree with the walk on the spelling, and two `format!`s in two files
800/// would be two chances to part over an underscore.
801#[must_use]
802pub fn runtime_function(method: &str) -> String {
803    format!("{}{method}", C::PREFIX)
804}
805
806/// The same, as text.
807///
808/// `default_c` writes the constant into an initialiser rather than into
809/// an expression tree, and a second `format!` in that file would be a
810/// second place the C spelling is decided.
811pub(crate) fn enumerator_constant_name(ast: &Ast, id: Id<TypeNode>, name: &str) -> String {
812    format!("{}{}", module_prefix(ast, id), c::escape_keyword(name))
813}