Skip to main content

synta_codegen/
c_codegen.rs

1//! C code generator using libcsynta API
2//!
3//! This module generates C code that uses the libcsynta C API to encode/decode
4//! ASN.1 structures defined in an ASN.1 module.
5
6use crate::ast::*;
7use crate::naming::{module_file_stem, to_pascal_case, to_screaming_snake_case, to_snake_case};
8use std::fmt::Write;
9
10/// Configuration options for C code generation
11#[derive(Debug, Clone, Default)]
12pub struct CCodeGenConfig {
13    /// Include path for synta.h (default: "synta.h")
14    pub synta_header_path: Option<String>,
15    /// Add static inline functions for common operations
16    pub generate_helpers: bool,
17    /// Generate SyntaArena type and _decode_arena() prototypes
18    pub arena_mode: bool,
19}
20
21impl CCodeGenConfig {
22    /// Create config with custom synta header path
23    pub fn with_header_path(path: impl Into<String>) -> Self {
24        Self {
25            synta_header_path: Some(path.into()),
26            generate_helpers: false,
27            arena_mode: false,
28        }
29    }
30
31    /// Enable generating helper functions
32    pub fn with_helpers(mut self) -> Self {
33        self.generate_helpers = true;
34        self
35    }
36
37    /// Enable arena/bump allocator mode
38    pub fn with_arena(mut self) -> Self {
39        self.arena_mode = true;
40        self
41    }
42}
43
44// ── Anonymous inner type expansion ───────────────────────────────────────────
45//
46// When an ASN.1 SEQUENCE field or CHOICE variant contains an anonymous inline
47// SEQUENCE / SET / CHOICE body, the Rust codegen extracts it into a named type
48// before the enclosing type.  The C codegen must do the same: C unions cannot
49// embed anonymous structs by value without a name, and the `void*` placeholder
50// we previously emitted for CHOICE variants was not type-safe.
51//
52// `expand_anonymous_types()` rewrites the definition list so that every
53// anonymous body becomes a named `Definition` inserted immediately before the
54// parent.  The parent's field/variant type is replaced by a `TypeRef` to the
55// new name.  The topological sort (`topo_order`) then handles ordering.
56
57/// Strip the outermost `Tagged` / `Constrained` wrapper and return `Some(body)`
58/// if the result is an anonymous inline SEQUENCE, SET, or CHOICE.
59fn anonymous_inner_c(ty: &Type) -> Option<&Type> {
60    let candidate = match ty {
61        Type::Tagged { inner, .. } => inner.as_ref(),
62        Type::Constrained { base_type, .. } => base_type.as_ref(),
63        other => other,
64    };
65    matches!(
66        candidate,
67        Type::Sequence(_) | Type::Set(_) | Type::Choice(_)
68    )
69    .then_some(candidate)
70}
71
72/// Re-wrap `inner` inside the outermost `Tagged` or `Constrained` layer of
73/// `original` so that tag annotation comments are preserved in the C output.
74fn rewrap_with_tag(original: &Type, inner: Type) -> Type {
75    match original {
76        Type::Tagged { tag, .. } => Type::Tagged {
77            tag: tag.clone(),
78            inner: Box::new(inner),
79        },
80        Type::Constrained { constraint, .. } => Type::Constrained {
81            base_type: Box::new(inner),
82            constraint: constraint.clone(),
83        },
84        _ => inner,
85    }
86}
87
88/// Expand anonymous inner types in `fields`, pushing synthetic `Definition`s
89/// into `synthetics` and replacing the inline body with a `TypeRef`.
90fn expand_fields(
91    fields: &[SequenceField],
92    parent_name: &str,
93    synthetics: &mut Vec<Definition>,
94) -> Vec<SequenceField> {
95    fields
96        .iter()
97        .map(|field| {
98            if let Some(body) = anonymous_inner_c(&field.ty) {
99                let syn_name = format!(
100                    "{}{}",
101                    to_pascal_case(parent_name),
102                    to_pascal_case(&field.name)
103                );
104                let expanded_body = expand_type(body, &syn_name, synthetics);
105                synthetics.push(Definition {
106                    name: syn_name.clone(),
107                    ty: expanded_body,
108                });
109                SequenceField {
110                    name: field.name.clone(),
111                    ty: rewrap_with_tag(&field.ty, Type::TypeRef(syn_name)),
112                    optional: field.optional,
113                    default: field.default.clone(),
114                }
115            } else {
116                field.clone()
117            }
118        })
119        .collect()
120}
121
122/// Expand anonymous inner types in `variants`, pushing synthetic `Definition`s
123/// into `synthetics` and replacing the inline body with a `TypeRef`.
124fn expand_variants(
125    variants: &[ChoiceVariant],
126    parent_name: &str,
127    synthetics: &mut Vec<Definition>,
128) -> Vec<ChoiceVariant> {
129    variants
130        .iter()
131        .map(|variant| {
132            if let Some(body) = anonymous_inner_c(&variant.ty) {
133                let syn_name = format!(
134                    "{}{}",
135                    to_pascal_case(parent_name),
136                    to_pascal_case(&variant.name)
137                );
138                let expanded_body = expand_type(body, &syn_name, synthetics);
139                synthetics.push(Definition {
140                    name: syn_name.clone(),
141                    ty: expanded_body,
142                });
143                ChoiceVariant {
144                    name: variant.name.clone(),
145                    ty: rewrap_with_tag(&variant.ty, Type::TypeRef(syn_name)),
146                }
147            } else {
148                variant.clone()
149            }
150        })
151        .collect()
152}
153
154/// Recursively expand anonymous inline bodies found inside `ty`.
155fn expand_type(ty: &Type, parent_name: &str, synthetics: &mut Vec<Definition>) -> Type {
156    match ty {
157        Type::Sequence(fields) => Type::Sequence(expand_fields(fields, parent_name, synthetics)),
158        Type::Set(fields) => Type::Set(expand_fields(fields, parent_name, synthetics)),
159        Type::Choice(variants) => Type::Choice(expand_variants(variants, parent_name, synthetics)),
160        other => other.clone(),
161    }
162}
163
164/// Expand all anonymous inline SEQUENCE / SET / CHOICE bodies in `defs` into
165/// named synthetic definitions, returning an extended definition list.
166///
167/// Each synthetic definition is inserted immediately before the parent so that
168/// `topo_order` will emit the helper struct before the struct that references
169/// it.  The naming convention is `{ParentPascalCase}{FieldOrVariantPascalCase}`,
170/// matching the Rust codegen convention.
171fn expand_anonymous_types(defs: &[Definition]) -> Vec<Definition> {
172    let mut result: Vec<Definition> = Vec::with_capacity(defs.len() * 2);
173    for def in defs {
174        let mut synthetics: Vec<Definition> = Vec::new();
175        let expanded_ty = expand_type(&def.ty, &def.name, &mut synthetics);
176        result.extend(synthetics);
177        result.push(Definition {
178            name: def.name.clone(),
179            ty: expanded_ty,
180        });
181    }
182    result
183}
184
185/// Generate C code from an ASN.1 module
186pub fn generate_c(module: &Module) -> Result<String, Box<dyn std::error::Error>> {
187    generate_c_with_config(module, CCodeGenConfig::default())
188}
189
190/// Generate C code with custom configuration
191pub fn generate_c_with_config(
192    module: &Module,
193    config: CCodeGenConfig,
194) -> Result<String, Box<dyn std::error::Error>> {
195    // Expand anonymous inline SEQUENCE / SET / CHOICE bodies into named types
196    // before any other processing so that forward declarations, topo-sort, and
197    // encoder/decoder prototypes all see the fully-named definition list.
198    let expanded_defs = expand_anonymous_types(&module.definitions);
199
200    let mut output = String::new();
201
202    // Header comment
203    writeln!(
204        &mut output,
205        "/* Generated from ASN.1 module {} */",
206        module.name
207    )?;
208    writeln!(&mut output, "/* DO NOT EDIT - auto-generated code */")?;
209    writeln!(&mut output)?;
210
211    // Include guards
212    let guard_name = format!("{}_H", to_screaming_snake_case(&module.name));
213    writeln!(&mut output, "#ifndef {}", guard_name)?;
214    writeln!(&mut output, "#define {}", guard_name)?;
215    writeln!(&mut output)?;
216
217    // Includes
218    writeln!(&mut output, "#include <stdint.h>")?;
219    writeln!(&mut output, "#include <stdbool.h>")?;
220    writeln!(&mut output, "#include <stdlib.h>")?;
221    writeln!(&mut output, "#include <string.h>")?;
222    if config.generate_helpers {
223        writeln!(&mut output, "#include <stdio.h>")?;
224    }
225    let header_path = config.synta_header_path.as_deref().unwrap_or("synta.h");
226    writeln!(&mut output, "#include \"{}\"", header_path)?;
227    writeln!(&mut output)?;
228
229    // Headers for imported modules — one #include per FROM clause
230    if !module.imports.is_empty() {
231        writeln!(&mut output, "/* Imported module headers */")?;
232        for import in &module.imports {
233            let stem = module_file_stem(&import.module_name);
234            writeln!(&mut output, "#include \"{}.h\"", stem)?;
235        }
236        writeln!(&mut output)?;
237    }
238
239    // Arena/bump allocator support
240    if config.arena_mode {
241        writeln!(&mut output, "/* Arena/bump allocator support */")?;
242        writeln!(&mut output)?;
243        writeln!(&mut output, "#ifndef SYNTA_ARENA_MAX_HANDLES")?;
244        writeln!(&mut output, "#define SYNTA_ARENA_MAX_HANDLES 256")?;
245        writeln!(&mut output, "#endif")?;
246        writeln!(&mut output)?;
247        writeln!(&mut output, "typedef struct {{")?;
248        writeln!(&mut output, "    void   *_ptrs[SYNTA_ARENA_MAX_HANDLES];")?;
249        writeln!(
250            &mut output,
251            "    void  (*_fns[SYNTA_ARENA_MAX_HANDLES])(void*);"
252        )?;
253        writeln!(&mut output, "    size_t  _n;")?;
254        writeln!(&mut output, "}} SyntaArena;")?;
255        writeln!(&mut output)?;
256        writeln!(
257            &mut output,
258            "static inline void synta_arena_init(SyntaArena *a) {{ a->_n = 0; }}"
259        )?;
260        writeln!(&mut output)?;
261        writeln!(
262            &mut output,
263            "static inline int _synta_arena_track(SyntaArena *a, void *p, void (*fn)(void*)) {{"
264        )?;
265        writeln!(
266            &mut output,
267            "    if (!p || a->_n >= SYNTA_ARENA_MAX_HANDLES) return 0;"
268        )?;
269        writeln!(
270            &mut output,
271            "    a->_ptrs[a->_n] = p; a->_fns[a->_n] = fn; a->_n++; return 1;"
272        )?;
273        writeln!(&mut output, "}}")?;
274        writeln!(&mut output)?;
275        writeln!(
276            &mut output,
277            "static inline void synta_arena_free_all(SyntaArena *a) {{"
278        )?;
279        writeln!(
280            &mut output,
281            "    for (size_t i = 0; i < a->_n; i++) a->_fns[i](a->_ptrs[i]);"
282        )?;
283        writeln!(&mut output, "    a->_n = 0;")?;
284        writeln!(&mut output, "}}")?;
285        writeln!(&mut output)?;
286    }
287
288    writeln!(&mut output, "#ifdef __cplusplus")?;
289    writeln!(&mut output, "extern \"C\" {{")?;
290    writeln!(&mut output, "#endif")?;
291    writeln!(&mut output)?;
292
293    // BitString support type
294    writeln!(&mut output, "/* BitString support */")?;
295    writeln!(&mut output)?;
296    writeln!(&mut output, "typedef struct {{")?;
297    writeln!(&mut output, "    SyntaByteArray data;")?;
298    writeln!(&mut output, "    uint8_t unused_bits;")?;
299    writeln!(&mut output, "}} SyntaBitString;")?;
300    writeln!(&mut output)?;
301
302    // OID arrays and other value constants from the module's value assignments
303    generate_value_constants(&mut output, module)?;
304
305    // Forward declarations for complex types
306    writeln!(&mut output, "/* Forward declarations */")?;
307    writeln!(&mut output)?;
308    for def in &expanded_defs {
309        match &def.ty {
310            Type::Sequence(_)
311            | Type::Set(_)
312            | Type::Choice(_)
313            | Type::SequenceOf(_, _)
314            | Type::SetOf(_, _) => {
315                let c_name = to_pascal_case(&def.name);
316                writeln!(&mut output, "typedef struct {} {};", c_name, c_name)?;
317            }
318            Type::Integer(_, named_numbers) if !named_numbers.is_empty() => {
319                let c_name = to_pascal_case(&def.name);
320                writeln!(&mut output, "typedef int64_t {};", c_name)?;
321            }
322            Type::Enumerated(_) => {
323                let c_name = to_pascal_case(&def.name);
324                writeln!(&mut output, "typedef enum {} {};", c_name, c_name)?;
325            }
326            _ => {}
327        }
328    }
329    writeln!(&mut output)?;
330
331    // Generate struct definitions in topological order so that embedded-value
332    // fields are always fully defined before the struct that contains them.
333    writeln!(&mut output, "/* Type definitions */")?;
334    writeln!(&mut output)?;
335    for idx in topo_order(&expanded_defs) {
336        generate_type_definition(&mut output, &expanded_defs[idx], config.generate_helpers)?;
337        writeln!(&mut output)?;
338    }
339
340    // Generate encoder/decoder function prototypes
341    writeln!(&mut output, "/* Encoder/Decoder functions */")?;
342    writeln!(&mut output)?;
343    for def in &expanded_defs {
344        generate_encoder_decoder_prototypes(&mut output, def, config.arena_mode)?;
345        writeln!(&mut output)?;
346    }
347
348    // Generate helper functions if enabled
349    if config.generate_helpers {
350        writeln!(&mut output, "/* Helper functions */")?;
351        writeln!(&mut output)?;
352        for def in &expanded_defs {
353            generate_helper_functions(&mut output, def)?;
354            writeln!(&mut output)?;
355        }
356    }
357
358    writeln!(&mut output, "#ifdef __cplusplus")?;
359    writeln!(&mut output, "}}")?;
360    writeln!(&mut output, "#endif")?;
361    writeln!(&mut output)?;
362
363    writeln!(&mut output, "#endif /* {} */", guard_name)?;
364
365    Ok(output)
366}
367
368/// Collect types that must be fully defined (not just forward-declared) before
369/// `ty` can be used as an embedded-value field.
370fn value_deps_of_type(ty: &Type, deps: &mut Vec<String>) {
371    match ty {
372        Type::Sequence(fields) | Type::Set(fields) => {
373            for field in fields {
374                value_deps_of_field_type(&field.ty, deps);
375            }
376        }
377        Type::Choice(variants) => {
378            // Union members are embedded by value, so TypeRef variants (possibly
379            // wrapped in a tag) create deps.
380            for variant in variants {
381                value_deps_of_field_type(&variant.ty, deps);
382            }
383        }
384        // SequenceOf/SetOf store elements as pointers – only a forward decl needed.
385        _ => {}
386    }
387}
388
389/// Collect the concrete `TypeRef` names that a struct/union *field* depends
390/// on by value (i.e., not through a pointer).
391///
392/// A `TypeRef` field is embedded by value in the generated C struct, so its
393/// definition must appear before the enclosing struct.  Anonymous inline
394/// SEQUENCE / SET fields are traversed recursively.  SEQUENCE OF / SET OF
395/// fields are pointers and only need a forward declaration, so they are
396/// ignored.  `Tagged` and `Constrained` wrappers are stripped to reach the
397/// underlying type.
398fn value_deps_of_field_type(ty: &Type, deps: &mut Vec<String>) {
399    match ty {
400        Type::TypeRef(name) => deps.push(name.clone()),
401        // Inline anonymous struct/set: recurse
402        Type::Sequence(inner_fields) | Type::Set(inner_fields) => {
403            for f in inner_fields {
404                value_deps_of_field_type(&f.ty, deps);
405            }
406        }
407        // Strip Tag/Constrained wrappers and check the real type
408        Type::Tagged { inner, .. }
409        | Type::Constrained {
410            base_type: inner, ..
411        } => {
412            value_deps_of_field_type(inner, deps);
413        }
414        // SequenceOf/SetOf: pointer field, forward decl is enough
415        _ => {}
416    }
417}
418
419/// Return indices into `defs` in a topological order dictated by embedded-value
420/// dependencies.  Falls back to the original position for any cycles.
421fn topo_order(defs: &[Definition]) -> Vec<usize> {
422    use std::collections::{HashMap, VecDeque};
423
424    let index: HashMap<&str, usize> = defs
425        .iter()
426        .enumerate()
427        .map(|(i, d)| (d.name.as_str(), i))
428        .collect();
429
430    let mut in_degree = vec![0usize; defs.len()];
431    // adj[j] = list of definition indices that depend on j
432    let mut adj: Vec<Vec<usize>> = vec![Vec::new(); defs.len()];
433
434    for (i, def) in defs.iter().enumerate() {
435        let mut raw_deps = Vec::new();
436        value_deps_of_type(&def.ty, &mut raw_deps);
437
438        // Deduplicate and build edges
439        let mut seen = std::collections::HashSet::new();
440        for dep in raw_deps {
441            if !seen.insert(dep.clone()) {
442                continue;
443            }
444            if let Some(&j) = index.get(dep.as_str()) {
445                if j != i && !adj[j].contains(&i) {
446                    adj[j].push(i);
447                    in_degree[i] += 1;
448                }
449            }
450        }
451    }
452
453    // Kahn's algorithm (FIFO preserves relative order of equal-degree nodes)
454    let mut queue: VecDeque<usize> = (0..defs.len()).filter(|&i| in_degree[i] == 0).collect();
455    let mut result = Vec::with_capacity(defs.len());
456
457    while let Some(i) = queue.pop_front() {
458        result.push(i);
459        for &j in &adj[i] {
460            in_degree[j] -= 1;
461            if in_degree[j] == 0 {
462                queue.push_back(j);
463            }
464        }
465    }
466
467    // Append any nodes involved in cycles (shouldn't happen in valid ASN.1)
468    for i in 0..defs.len() {
469        if !result.contains(&i) {
470            result.push(i);
471        }
472    }
473
474    result
475}
476
477/// Format a constraint as a human-readable string for C comments.
478fn format_c_constraint_display(constraint: &SubtypeConstraint) -> String {
479    match constraint {
480        SubtypeConstraint::SingleValue(val) => match val {
481            ConstraintValue::Integer(n) => n.to_string(),
482            ConstraintValue::Min => "MIN".to_string(),
483            ConstraintValue::Max => "MAX".to_string(),
484            ConstraintValue::NamedValue(name) => name.clone(),
485        },
486        SubtypeConstraint::ValueRange { min, max } => {
487            let min_str = match min {
488                ConstraintValue::Integer(n) => n.to_string(),
489                ConstraintValue::Min => "MIN".to_string(),
490                ConstraintValue::Max => "MAX".to_string(),
491                ConstraintValue::NamedValue(n) => n.clone(),
492            };
493            let max_str = match max {
494                ConstraintValue::Integer(n) => n.to_string(),
495                ConstraintValue::Max => "MAX".to_string(),
496                ConstraintValue::Min => "MIN".to_string(),
497                ConstraintValue::NamedValue(n) => n.clone(),
498            };
499            format!("{}..{}", min_str, max_str)
500        }
501        SubtypeConstraint::Union(elements) => {
502            let parts: Vec<String> = elements.iter().map(format_c_constraint_display).collect();
503            parts.join(" | ")
504        }
505        SubtypeConstraint::Intersection(elements) => {
506            let parts: Vec<String> = elements
507                .iter()
508                .map(|e| format!("({})", format_c_constraint_display(e)))
509                .collect();
510            parts.join(" ^ ")
511        }
512        SubtypeConstraint::Complement(inner) => {
513            format!("ALL EXCEPT {}", format_c_constraint_display(inner))
514        }
515        SubtypeConstraint::SizeConstraint(inner) => {
516            format!("SIZE ({})", format_c_constraint_display(inner))
517        }
518        SubtypeConstraint::Pattern(p) => format!("PATTERN \"{}\"", p),
519        SubtypeConstraint::PermittedAlphabet(ranges) => {
520            let parts: Vec<String> = ranges
521                .iter()
522                .map(|r| {
523                    if r.min == r.max {
524                        format!("\"{}\"", r.min)
525                    } else {
526                        format!("\"{}\"..\"{}\"", r.min, r.max)
527                    }
528                })
529                .collect();
530            format!("FROM ({})", parts.join(" | "))
531        }
532        _ => "constraint".to_string(),
533    }
534}
535
536/// Return the smallest C integer type whose range covers all values permitted
537/// by `constraint`.
538///
539/// When the lower bound is ≥ 0 (non-negative), unsigned types are preferred:
540/// `uint8_t` (0..=255), `uint16_t` (0..=65535), `uint32_t` (0..=4294967295),
541/// `uint64_t`.
542///
543/// When the lower bound is negative, the smallest signed type that fits both
544/// bounds is chosen: `int8_t`, `int16_t`, `int32_t`, `int64_t`.
545///
546/// Falls back to `int64_t` / `uint64_t` when either bound is `MIN`, `MAX`, a
547/// named value, or the constraint is not a simple value/range.
548fn constrained_integer_c_type(constraint: &SubtypeConstraint) -> &'static str {
549    let (lo, hi) = match constraint {
550        SubtypeConstraint::SingleValue(ConstraintValue::Integer(n)) => (*n, *n),
551        SubtypeConstraint::ValueRange {
552            min: ConstraintValue::Integer(lo),
553            max: ConstraintValue::Integer(hi),
554        } => (*lo, *hi),
555        _ => return "int64_t",
556    };
557    if lo >= 0 {
558        if hi <= u8::MAX as i64 {
559            "uint8_t"
560        } else if hi <= u16::MAX as i64 {
561            "uint16_t"
562        } else if hi <= u32::MAX as i64 {
563            "uint32_t"
564        } else {
565            "uint64_t"
566        }
567    } else if lo >= i8::MIN as i64 && hi <= i8::MAX as i64 {
568        "int8_t"
569    } else if lo >= i16::MIN as i64 && hi <= i16::MAX as i64 {
570        "int16_t"
571    } else if lo >= i32::MIN as i64 && hi <= i32::MAX as i64 {
572        "int32_t"
573    } else {
574        "int64_t"
575    }
576}
577
578/// Generate a C boolean expression checking whether `var` (a C integer lvalue
579/// of type `c_type`) satisfies `constraint`.
580///
581/// When `c_type` is an unsigned integer type (`uint8_t` etc.), lower bounds
582/// that are ≤ 0 are omitted because unsigned variables can never be negative —
583/// emitting `v >= 0` for a `uint8_t` would always be true and trigger compiler
584/// warnings.
585///
586/// Returns `"1"` when no bounds can be violated (e.g., unconstrained `MIN..MAX`),
587/// and `"1 /* unsupported constraint */"` for constraint kinds not yet handled.
588fn generate_c_constraint_check(var: &str, constraint: &SubtypeConstraint, c_type: &str) -> String {
589    let is_unsigned = c_type.starts_with("uint");
590    match constraint {
591        SubtypeConstraint::SingleValue(val) => match val {
592            ConstraintValue::Integer(n) => format!("{} == {}LL", var, n),
593            ConstraintValue::Min => format!("{} == INT64_MIN", var),
594            ConstraintValue::Max => format!("{} == INT64_MAX", var),
595            ConstraintValue::NamedValue(name) => format!("{} == {}", var, name),
596        },
597        SubtypeConstraint::ValueRange { min, max } => {
598            let mut parts: Vec<String> = Vec::new();
599            match min {
600                // For unsigned types a lower bound of ≤ 0 is trivially satisfied.
601                ConstraintValue::Integer(n) if is_unsigned && *n <= 0 => {}
602                ConstraintValue::Integer(n) => parts.push(format!("{} >= {}LL", var, n)),
603                ConstraintValue::Min => {}
604                ConstraintValue::Max => parts.push(format!("{} >= INT64_MAX", var)),
605                ConstraintValue::NamedValue(name) => parts.push(format!("{} >= {}", var, name)),
606            }
607            match max {
608                ConstraintValue::Integer(n) => parts.push(format!("{} <= {}LL", var, n)),
609                ConstraintValue::Max => {}
610                ConstraintValue::Min => parts.push(format!("{} <= INT64_MIN", var)),
611                ConstraintValue::NamedValue(name) => parts.push(format!("{} <= {}", var, name)),
612            }
613            if parts.is_empty() {
614                "1".to_string()
615            } else {
616                format!("({})", parts.join(" && "))
617            }
618        }
619        SubtypeConstraint::Union(elements) => {
620            let checks: Vec<String> = elements
621                .iter()
622                .map(|e| generate_c_constraint_check(var, e, c_type))
623                .collect();
624            format!("({})", checks.join(" || "))
625        }
626        SubtypeConstraint::Intersection(elements) => {
627            let checks: Vec<String> = elements
628                .iter()
629                .map(|e| generate_c_constraint_check(var, e, c_type))
630                .collect();
631            format!("({})", checks.join(" && "))
632        }
633        SubtypeConstraint::Complement(inner) => {
634            let inner_check = generate_c_constraint_check(var, inner, c_type);
635            format!("!({})", inner_check)
636        }
637        _ => "1 /* unsupported constraint */".to_string(),
638    }
639}
640
641/// Generate the C typedef and `static inline` helpers for a top-level constrained INTEGER.
642///
643/// The storage type is the smallest C integer type that fits the constraint
644/// range.  When the lower bound is ≥ 0, an unsigned type is chosen
645/// (`uint8_t` / `uint16_t` / `uint32_t` / `uint64_t`); when the lower bound
646/// is negative, a signed type is chosen (`int8_t` / `int16_t` / `int32_t` /
647/// `int64_t`).  Unconstrained bounds fall back to `int64_t`.
648///
649/// For unsigned storage types, lower-bound checks that are trivially true
650/// (i.e. the bound is ≤ 0) are omitted from the generated validation expression
651/// to avoid compiler warnings about always-true comparisons.
652///
653/// Emits, in order:
654/// 1. `typedef struct { uintN_t value; } FooType;`  (or `intN_t` for signed)
655/// 2. `bool foo_type_new(uintN_t v, FooType *out)` — validated constructor
656/// 3. `FooType foo_type_new_unchecked(uintN_t v)` — unchecked constructor
657/// 4. `uintN_t foo_type_get(const FooType *self)` — value accessor
658/// 5. `bool foo_type_validate(const FooType *self)` — re-validate
659///
660/// Optionally also emits `#define` constants for any named numbers.
661fn generate_constrained_integer_c(
662    output: &mut String,
663    name: &str,
664    constraint: &SubtypeConstraint,
665    named_numbers: &[NamedNumber],
666) -> Result<(), Box<dyn std::error::Error>> {
667    let c_name = to_pascal_case(name);
668    let fn_prefix = to_snake_case(name);
669    let c_type = constrained_integer_c_type(constraint);
670    let display = format_c_constraint_display(constraint);
671    let check = generate_c_constraint_check("v", constraint, c_type);
672
673    // Struct typedef
674    writeln!(output, "/* INTEGER ({}) */", display)?;
675    writeln!(output, "typedef struct {{ {} value; }} {};", c_type, c_name)?;
676    writeln!(output)?;
677
678    // Named-value constants (e.g. from MsgType ::= INTEGER (10..19) { AS_REQ(10), ... })
679    if !named_numbers.is_empty() {
680        writeln!(output, "/* Named values for {} */", c_name)?;
681        for nn in named_numbers {
682            let const_name = to_screaming_snake_case(&nn.name);
683            writeln!(
684                output,
685                "#define {}_{} (({}){})",
686                c_name, const_name, c_type, nn.value
687            )?;
688        }
689        writeln!(output)?;
690    }
691
692    // _new() — validated constructor
693    writeln!(
694        output,
695        "/** Create {}: validates INTEGER ({}). Returns false if out of range. */",
696        c_name, display
697    )?;
698    writeln!(
699        output,
700        "static inline bool {}_new({} v, {}* out) {{",
701        fn_prefix, c_type, c_name
702    )?;
703    writeln!(output, "    if (!({check})) return false;", check = check)?;
704    writeln!(output, "    out->value = v;")?;
705    writeln!(output, "    return true;")?;
706    writeln!(output, "}}")?;
707    writeln!(output)?;
708
709    // _new_unchecked() — bypass validation
710    writeln!(
711        output,
712        "/** Create {} without validation (use with caution). */",
713        c_name
714    )?;
715    writeln!(
716        output,
717        "static inline {} {}_new_unchecked({} v) {{",
718        c_name, fn_prefix, c_type
719    )?;
720    writeln!(output, "    {} out; out.value = v; return out;", c_name)?;
721    writeln!(output, "}}")?;
722    writeln!(output)?;
723
724    // _get() — value accessor
725    writeln!(
726        output,
727        "/** Get the inner {} value of {}. */",
728        c_type, c_name
729    )?;
730    writeln!(
731        output,
732        "static inline {} {}_get(const {}* self) {{ return self->value; }}",
733        c_type, fn_prefix, c_name
734    )?;
735    writeln!(output)?;
736
737    // _validate() — re-validate a value already stored in the struct
738    writeln!(
739        output,
740        "/** Check that {} satisfies INTEGER ({}). */",
741        c_name, display
742    )?;
743    writeln!(
744        output,
745        "static inline bool {}_validate(const {}* self) {{",
746        fn_prefix, c_name
747    )?;
748    writeln!(output, "    {} v = self->value;", c_type)?;
749    writeln!(output, "    return {check};", check = check)?;
750    writeln!(output, "}}")?;
751
752    Ok(())
753}
754
755/// Return the ASN.1 base-type name used in generated comments for string types.
756fn string_base_type_name(ty: &Type) -> &'static str {
757    match ty {
758        Type::IA5String(_) => "IA5String",
759        Type::PrintableString(_) => "PrintableString",
760        Type::Utf8String(_) => "UTF8String",
761        Type::OctetString(_) => "OCTET STRING",
762        Type::BitString(_) => "BIT STRING",
763        _ => "STRING",
764    }
765}
766
767/// Generate a C boolean expression checking a `uint32_t` length variable against
768/// a SIZE sub-constraint.
769///
770/// For a `MIN..MAX` or `0..MAX` range all `uint32_t` values satisfy the bound, so
771/// those bounds are omitted and `"1"` is returned when no check remains.
772fn generate_c_length_check(len_var: &str, size_constraint: &SubtypeConstraint) -> String {
773    match size_constraint {
774        SubtypeConstraint::SingleValue(val) => match val {
775            ConstraintValue::Integer(n) => format!("{} == {}U", len_var, n),
776            ConstraintValue::Min | ConstraintValue::Max => "1".to_string(),
777            ConstraintValue::NamedValue(name) => format!("{} == {}", len_var, name),
778        },
779        SubtypeConstraint::ValueRange { min, max } => {
780            let mut parts: Vec<String> = Vec::new();
781            match min {
782                ConstraintValue::Integer(n) if *n > 0 => {
783                    parts.push(format!("{} >= {}U", len_var, n));
784                }
785                // 0 or MIN: always true for uint32_t, skip
786                ConstraintValue::Integer(_) | ConstraintValue::Min => {}
787                ConstraintValue::Max => parts.push(format!("{} >= UINT32_MAX", len_var)),
788                ConstraintValue::NamedValue(name) => {
789                    parts.push(format!("{} >= {}", len_var, name));
790                }
791            }
792            match max {
793                ConstraintValue::Integer(n) => parts.push(format!("{} <= {}U", len_var, n)),
794                ConstraintValue::Max => {}
795                ConstraintValue::Min => parts.push(format!("{} == 0", len_var)),
796                ConstraintValue::NamedValue(name) => {
797                    parts.push(format!("{} <= {}", len_var, name));
798                }
799            }
800            if parts.is_empty() {
801                "1".to_string()
802            } else {
803                format!("({})", parts.join(" && "))
804            }
805        }
806        SubtypeConstraint::Union(elements) => {
807            let checks: Vec<String> = elements
808                .iter()
809                .map(|e| generate_c_length_check(len_var, e))
810                .collect();
811            format!("({})", checks.join(" || "))
812        }
813        SubtypeConstraint::Intersection(elements) => {
814            let checks: Vec<String> = elements
815                .iter()
816                .map(|e| generate_c_length_check(len_var, e))
817                .collect();
818            format!("({})", checks.join(" && "))
819        }
820        SubtypeConstraint::Complement(inner) => {
821            let inner_check = generate_c_length_check(len_var, inner);
822            format!("!({})", inner_check)
823        }
824        _ => "1 /* unsupported size constraint */".to_string(),
825    }
826}
827
828/// Format a char as a C character literal (ASCII printable or `\xNN` escape).
829pub(crate) fn format_c_char_literal(c: char) -> String {
830    if c == '\'' || c == '\\' {
831        format!("'\\{}'", c)
832    } else if c.is_ascii() && (c as u8) >= 0x20 && (c as u8) < 0x7f {
833        format!("'{}'", c)
834    } else {
835        format!("'\\x{:02x}'", c as u32)
836    }
837}
838
839/// Generate the C boolean sub-expression for a single `FROM` alphabet range set.
840///
841/// Returns an expression over the local variable `_c` (type `unsigned char`)
842/// that is true when `_c` falls within one of the permitted character ranges.
843pub(crate) fn generate_c_alphabet_expr(ranges: &[CharRange]) -> String {
844    if ranges.is_empty() {
845        return "1 /* no alphabet constraint */".to_string();
846    }
847    let parts: Vec<String> = ranges
848        .iter()
849        .map(|r| {
850            if r.min == r.max {
851                format!("_c == {}", format_c_char_literal(r.min))
852            } else {
853                format!(
854                    "(_c >= {} && _c <= {})",
855                    format_c_char_literal(r.min),
856                    format_c_char_literal(r.max)
857                )
858            }
859        })
860        .collect();
861    parts.join(" || ")
862}
863
864/// Emit C validation statements for a string subtype constraint into `out`.
865///
866/// * `indent`     — indentation prefix for each emitted line.
867/// * `len_var`    — name of the already-declared `uint32_t` length variable.
868/// * `value_expr` — C expression of type `SyntaByteArray` (e.g. `"value"` or `"self->value"`).
869fn emit_c_string_validation_stmts(
870    out: &mut String,
871    indent: &str,
872    constraint: &SubtypeConstraint,
873    len_var: &str,
874    value_expr: &str,
875) -> Result<(), Box<dyn std::error::Error>> {
876    match constraint {
877        SubtypeConstraint::SizeConstraint(inner) => {
878            let check = generate_c_length_check(len_var, inner);
879            writeln!(
880                out,
881                "{}if (!({check})) return false;",
882                indent,
883                check = check
884            )?;
885        }
886        SubtypeConstraint::PermittedAlphabet(ranges) => {
887            let alpha_expr = generate_c_alphabet_expr(ranges);
888            writeln!(out, "{}{{", indent)?;
889            writeln!(out, "{}    uint32_t _i;", indent)?;
890            writeln!(
891                out,
892                "{}    const unsigned char *_ap = (const unsigned char *){}.data;",
893                indent, value_expr
894            )?;
895            writeln!(out, "{}    bool _ok = true;", indent)?;
896            writeln!(
897                out,
898                "{}    for (_i = 0; _i < {lv} && _ok; _i++) {{",
899                indent,
900                lv = len_var
901            )?;
902            writeln!(out, "{}        unsigned char _c = _ap[_i];", indent)?;
903            writeln!(out, "{}        _ok = {alpha};", indent, alpha = alpha_expr)?;
904            writeln!(out, "{}    }}", indent)?;
905            writeln!(out, "{}    if (!_ok) return false;", indent)?;
906            writeln!(out, "{}}}", indent)?;
907        }
908        SubtypeConstraint::Pattern(p) => {
909            writeln!(
910                out,
911                "{}/* PATTERN constraint \"{}\" not enforced at runtime; use --with-regex or --with-pcre with --impl (single-file) or --emit impl/both (multi-file) to enable */",
912                indent, p
913            )?;
914        }
915        SubtypeConstraint::ContainedSubtype(_ty) => {
916            writeln!(
917                out,
918                "{}/* CONTAINING constraint not enforced at runtime; use --with-containing with --impl (single-file) or --emit impl/both (multi-file) to enable */",
919                indent
920            )?;
921        }
922        SubtypeConstraint::Intersection(parts) => {
923            for part in parts {
924                emit_c_string_validation_stmts(out, indent, part, len_var, value_expr)?;
925            }
926        }
927        SubtypeConstraint::Union(_) => {
928            writeln!(
929                out,
930                "{}/* Union constraint: complex, treated as unchecked */",
931                indent
932            )?;
933        }
934        SubtypeConstraint::Complement(_) => {
935            writeln!(
936                out,
937                "{}/* Complement constraint: complex, treated as unchecked */",
938                indent
939            )?;
940        }
941        _ => {
942            writeln!(out, "{}/* unsupported constraint: skipped */", indent)?;
943        }
944    }
945    Ok(())
946}
947
948/// Generate a typedef and named bit constants for a BIT STRING with a `NamedBitList` constraint.
949///
950/// Emits, in order:
951/// 1. `typedef SyntaBitString TypeName;`
952/// 2. `#define TYPE_NAME_BITNAME_BIT  N` for every named bit (values aligned)
953/// 3. (with helpers) `#define TYPE_NAME_IS_SET / _SET / _CLEAR` helper macros
954///    that forward to `synta_bitstring_is_set / _set / _clear` from `synta.h`.
955fn generate_named_bit_string_c(
956    output: &mut String,
957    name: &str,
958    bits: &[NamedNumber],
959    generate_helpers: bool,
960) -> Result<(), Box<dyn std::error::Error>> {
961    let struct_name = to_pascal_case(name);
962    // Prefix for #define names: derive from the raw ASN.1 identifier so that
963    // hyphenated names like "kdc-options" produce "KDC_OPTIONS".
964    let prefix = to_screaming_snake_case(name);
965
966    writeln!(output, "/* {} — BIT STRING with named bits */", struct_name)?;
967    writeln!(output, "typedef SyntaBitString {};", struct_name)?;
968
969    if bits.is_empty() {
970        return Ok(());
971    }
972
973    writeln!(output)?;
974    writeln!(output, "/* Named bit positions for {} */", struct_name)?;
975
976    // Collect macro names for alignment.
977    let macro_names: Vec<String> = bits
978        .iter()
979        .map(|b| {
980            let bit_upper = to_snake_case(&b.name).to_uppercase();
981            format!("{prefix}_{bit_upper}_BIT")
982        })
983        .collect();
984    let max_len = macro_names.iter().map(|s| s.len()).max().unwrap_or(0);
985
986    for (bit, macro_name) in bits.iter().zip(&macro_names) {
987        writeln!(
988            output,
989            "#define {macro_name:width$} {val}",
990            width = max_len,
991            val = bit.value
992        )?;
993    }
994
995    if generate_helpers {
996        writeln!(output)?;
997        writeln!(output, "/* Bit-operation helpers for {} */", struct_name)?;
998        writeln!(
999            output,
1000            "/* (requires synta_bitstring_is_set/set/clear from synta.h) */"
1001        )?;
1002        let is_set = format!("{prefix}_IS_SET(bs, bit)");
1003        let set = format!("{prefix}_SET(bs, bit)");
1004        let clear = format!("{prefix}_CLEAR(bs, bit)");
1005        let helper_max = [is_set.len(), set.len(), clear.len()]
1006            .iter()
1007            .copied()
1008            .max()
1009            .unwrap_or(0);
1010        writeln!(
1011            output,
1012            "#define {is_set:width$} synta_bitstring_is_set(&(bs).data, (bit))",
1013            width = helper_max
1014        )?;
1015        writeln!(
1016            output,
1017            "#define {set:width$} synta_bitstring_set(&(bs).data, (bit))",
1018            width = helper_max
1019        )?;
1020        writeln!(
1021            output,
1022            "#define {clear:width$} synta_bitstring_clear(&(bs).data, (bit))",
1023            width = helper_max
1024        )?;
1025    }
1026
1027    Ok(())
1028}
1029
1030/// Generate the C typedef and `static inline` helpers for a top-level constrained string type.
1031///
1032/// Emits, in order:
1033/// 1. `typedef struct { SyntaByteArray value; } FooType;`
1034/// 2. `bool foo_type_new(SyntaByteArray value, FooType *out)` — validated constructor
1035/// 3. `FooType foo_type_new_unchecked(SyntaByteArray value)` — unchecked constructor
1036/// 4. `SyntaByteArray foo_type_get(const FooType *self)` — borrowed value (owned cleared)
1037/// 5. `bool foo_type_validate(const FooType *self)` — re-validate stored value
1038/// 6. `void foo_type_free(FooType *self)` — free buffer if owned
1039fn generate_constrained_string_c(
1040    output: &mut String,
1041    name: &str,
1042    base_type_str: &str,
1043    constraint: &SubtypeConstraint,
1044) -> Result<(), Box<dyn std::error::Error>> {
1045    let c_name = to_pascal_case(name);
1046    let fn_prefix = to_snake_case(name);
1047    let display = format_c_constraint_display(constraint);
1048
1049    // Struct typedef
1050    writeln!(output, "/* {} ({}) */", base_type_str, display)?;
1051    writeln!(
1052        output,
1053        "typedef struct {{ SyntaByteArray value; }} {};",
1054        c_name
1055    )?;
1056    writeln!(output)?;
1057
1058    // _new() — validated constructor
1059    writeln!(
1060        output,
1061        "/** Create {c}: validates {bt} ({d}). Returns false if constraint violated. */",
1062        c = c_name,
1063        bt = base_type_str,
1064        d = display
1065    )?;
1066    writeln!(
1067        output,
1068        "static inline bool {fn}_new(SyntaByteArray value, {c}* out) {{",
1069        fn = fn_prefix,
1070        c = c_name
1071    )?;
1072    writeln!(output, "    uint32_t _len = value.len;")?;
1073    emit_c_string_validation_stmts(output, "    ", constraint, "_len", "value")?;
1074    writeln!(output, "    out->value = value;")?;
1075    writeln!(output, "    return true;")?;
1076    writeln!(output, "}}")?;
1077    writeln!(output)?;
1078
1079    // _new_unchecked() — bypass validation
1080    writeln!(
1081        output,
1082        "/** Create {} without validation (use with caution). */",
1083        c_name
1084    )?;
1085    writeln!(
1086        output,
1087        "static inline {c} {fn}_new_unchecked(SyntaByteArray value) {{",
1088        c = c_name,
1089        fn = fn_prefix
1090    )?;
1091    writeln!(
1092        output,
1093        "    {c} out; out.value = value; return out;",
1094        c = c_name
1095    )?;
1096    writeln!(output, "}}")?;
1097    writeln!(output)?;
1098
1099    // _get() — borrowed accessor (ownership cleared to prevent double-free)
1100    writeln!(
1101        output,
1102        "/** Get a borrowed view of the {} value (owned flag cleared). */",
1103        c_name
1104    )?;
1105    writeln!(
1106        output,
1107        "static inline SyntaByteArray {fn}_get(const {c}* self) {{",
1108        fn = fn_prefix,
1109        c = c_name
1110    )?;
1111    writeln!(
1112        output,
1113        "    SyntaByteArray r = self->value; r.owned = 0; return r;"
1114    )?;
1115    writeln!(output, "}}")?;
1116    writeln!(output)?;
1117
1118    // _validate() — re-validate the stored value
1119    writeln!(
1120        output,
1121        "/** Check that {} satisfies {} ({}). */",
1122        c_name, base_type_str, display
1123    )?;
1124    writeln!(
1125        output,
1126        "static inline bool {fn}_validate(const {c}* self) {{",
1127        fn = fn_prefix,
1128        c = c_name
1129    )?;
1130    writeln!(output, "    uint32_t _len = self->value.len;")?;
1131    emit_c_string_validation_stmts(output, "    ", constraint, "_len", "self->value")?;
1132    writeln!(output, "    return true;")?;
1133    writeln!(output, "}}")?;
1134    writeln!(output)?;
1135
1136    // _free() — release owned buffer
1137    writeln!(
1138        output,
1139        "/** Free {} if its value buffer is owned. */",
1140        c_name
1141    )?;
1142    writeln!(
1143        output,
1144        "static inline void {fn}_free({c}* self) {{",
1145        fn = fn_prefix,
1146        c = c_name
1147    )?;
1148    writeln!(
1149        output,
1150        "    if (self->value.owned != 0) {{ synta_byte_array_free(&self->value); self->value.owned = 0; }}"
1151    )?;
1152    writeln!(output, "}}")?;
1153
1154    Ok(())
1155}
1156
1157/// Generate a type definition (struct, enum, typedef)
1158fn generate_type_definition(
1159    output: &mut String,
1160    def: &Definition,
1161    generate_helpers: bool,
1162) -> Result<(), Box<dyn std::error::Error>> {
1163    match &def.ty {
1164        Type::Sequence(fields) | Type::Set(fields) => {
1165            generate_sequence_struct(output, &def.name, fields)?;
1166        }
1167        Type::Choice(variants) => {
1168            generate_choice_struct(output, &def.name, variants)?;
1169        }
1170        Type::Integer(_, named_numbers) if !named_numbers.is_empty() => {
1171            generate_defines_for_integer(output, &def.name, named_numbers)?;
1172        }
1173        Type::Enumerated(named_values) => {
1174            generate_enum_for_integer(output, &def.name, named_values)?;
1175        }
1176        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
1177            // Generate a struct with count and array for SEQUENCE OF / SET OF.
1178            // The typedef was already emitted in the forward-declarations section,
1179            // so use plain struct syntax here.
1180            let struct_name = to_pascal_case(&def.name);
1181            let elem_type = get_c_type(inner);
1182            writeln!(output, "struct {} {{", struct_name)?;
1183            writeln!(output, "    size_t count;")?;
1184            writeln!(output, "    {}* items;", elem_type)?;
1185            writeln!(output, "}};")?;
1186        }
1187        Type::Constrained {
1188            base_type,
1189            constraint,
1190        } => {
1191            match (base_type.as_ref(), &constraint.spec) {
1192                (Type::Integer(_, named_numbers), ConstraintSpec::Subtype(subtype)) => {
1193                    generate_constrained_integer_c(output, &def.name, subtype, named_numbers)?;
1194                }
1195                // BIT STRING with named bits — must come before the general string arm.
1196                (
1197                    Type::BitString(_),
1198                    ConstraintSpec::Subtype(SubtypeConstraint::NamedBitList(bits)),
1199                ) => {
1200                    generate_named_bit_string_c(output, &def.name, bits, generate_helpers)?;
1201                }
1202                // BIT STRING { bits } (SIZE N..MAX) — the parser produces
1203                // Intersection([NamedBitList(bits), SizeConstraint(...)]).
1204                // Extract the named bits and treat this as a named-bit string;
1205                // the SIZE component is advisory information carried inline in
1206                // the ASN.1 schema and does not change the C representation.
1207                (
1208                    Type::BitString(_),
1209                    ConstraintSpec::Subtype(SubtypeConstraint::Intersection(parts)),
1210                ) if parts
1211                    .iter()
1212                    .any(|p| matches!(p, SubtypeConstraint::NamedBitList(_))) =>
1213                {
1214                    let bits = parts
1215                        .iter()
1216                        .find_map(|p| {
1217                            if let SubtypeConstraint::NamedBitList(b) = p {
1218                                Some(b.as_slice())
1219                            } else {
1220                                None
1221                            }
1222                        })
1223                        .unwrap_or(&[]);
1224                    generate_named_bit_string_c(output, &def.name, bits, generate_helpers)?;
1225                }
1226                (
1227                    Type::IA5String(_)
1228                    | Type::PrintableString(_)
1229                    | Type::Utf8String(_)
1230                    | Type::OctetString(_)
1231                    | Type::BitString(_),
1232                    ConstraintSpec::Subtype(subtype),
1233                ) => {
1234                    let base_name = string_base_type_name(base_type);
1235                    generate_constrained_string_c(output, &def.name, base_name, subtype)?;
1236                }
1237                _ => {
1238                    // Other constrained types — emit a bare typedef.
1239                    let c_name = to_pascal_case(&def.name);
1240                    let base_c_type = get_c_type(base_type);
1241                    writeln!(
1242                        output,
1243                        "/* Constrained type: constraint validation not yet implemented */"
1244                    )?;
1245                    writeln!(output, "typedef {} {};", base_c_type, c_name)?;
1246                }
1247            }
1248        }
1249        Type::TypeRef(_) => {
1250            // Type alias - just a typedef
1251            let c_name = to_pascal_case(&def.name);
1252            let base_type = get_c_type(&def.ty);
1253            writeln!(output, "typedef {} {};", base_type, c_name)?;
1254        }
1255        _ => {
1256            // Simple type alias
1257            let c_name = to_pascal_case(&def.name);
1258            let base_type = get_c_type(&def.ty);
1259            writeln!(output, "typedef {} {};", base_type, c_name)?;
1260        }
1261    }
1262    Ok(())
1263}
1264
1265/// Return an inline C comment for a tag annotation, e.g. `"[0] EXPLICIT"` or
1266/// `"[APPLICATION 1] IMPLICIT"`, if the outermost layer of `ty` is `Tagged`.
1267/// Returns `None` for all other types.
1268fn tag_annotation_comment(ty: &Type) -> Option<String> {
1269    if let Type::Tagged {
1270        tag: tag_info,
1271        inner,
1272    } = ty
1273    {
1274        let cls = match tag_info.class {
1275            TagClass::Universal => format!("UNIVERSAL {}", tag_info.number),
1276            TagClass::Application => format!("APPLICATION {}", tag_info.number),
1277            TagClass::ContextSpecific => tag_info.number.to_string(),
1278            TagClass::Private => format!("PRIVATE {}", tag_info.number),
1279        };
1280        // ASN.1 X.680 §31.2.7: IMPLICIT tagging cannot be applied to CHOICE or ANY types;
1281        // such a tag is automatically treated as EXPLICIT.
1282        let inner_is_choice_or_any = matches!(
1283            inner.as_ref(),
1284            Type::Choice(_) | Type::Any | Type::AnyDefinedBy(_)
1285        );
1286        let mode = match tag_info.tagging {
1287            Tagging::Explicit => "EXPLICIT",
1288            Tagging::Implicit if inner_is_choice_or_any => "EXPLICIT",
1289            Tagging::Implicit => "IMPLICIT",
1290        };
1291        Some(format!("[{}] {}", cls, mode))
1292    } else {
1293        None
1294    }
1295}
1296
1297/// Generate a struct definition for SEQUENCE type
1298fn generate_sequence_struct(
1299    output: &mut String,
1300    name: &str,
1301    fields: &[SequenceField],
1302) -> Result<(), Box<dyn std::error::Error>> {
1303    let struct_name = to_pascal_case(name);
1304    writeln!(output, "struct {} {{", struct_name)?;
1305
1306    for field in fields {
1307        // Skip NULL fields as they don't have data
1308        if matches!(field.ty, Type::Null) {
1309            continue;
1310        }
1311
1312        let field_name = to_snake_case(&field.name);
1313
1314        // Handle inline SEQUENCE/SET types
1315        match &field.ty {
1316            Type::Sequence(inner_fields) | Type::Set(inner_fields) => {
1317                // Generate inline anonymous struct
1318                if field.optional {
1319                    writeln!(output, "    bool has_{};", field_name)?;
1320                }
1321                writeln!(output, "    struct {{")?;
1322                for inner_field in inner_fields {
1323                    if matches!(inner_field.ty, Type::Null) {
1324                        continue;
1325                    }
1326                    let inner_name = to_snake_case(&inner_field.name);
1327                    let inner_type = get_c_type_for_field(&inner_field.ty);
1328                    if inner_field.optional {
1329                        writeln!(output, "        bool has_{};", inner_name)?;
1330                        writeln!(output, "        {} {};", inner_type, inner_name)?;
1331                    } else {
1332                        writeln!(output, "        {} {};", inner_type, inner_name)?;
1333                    }
1334                }
1335                writeln!(output, "    }} {};", field_name)?;
1336            }
1337            _ => {
1338                let field_type = get_c_type_for_field(&field.ty);
1339                let tag_note = tag_annotation_comment(&field.ty);
1340
1341                // For SEQUENCE OF / SET OF, generate length field
1342                if matches!(field.ty, Type::SequenceOf(_, _) | Type::SetOf(_, _)) {
1343                    writeln!(output, "    size_t {}_count;", field_name)?;
1344                    writeln!(output, "    {} {};", field_type, field_name)?;
1345                } else if field.optional {
1346                    writeln!(output, "    bool has_{};", field_name)?;
1347                    if let Some(ref tn) = tag_note {
1348                        writeln!(output, "    {} {}; /* {} */", field_type, field_name, tn)?;
1349                    } else {
1350                        writeln!(output, "    {} {};", field_type, field_name)?;
1351                    }
1352                } else if let Some(ref dv) = field.default {
1353                    if let Some(ref tn) = tag_note {
1354                        writeln!(
1355                            output,
1356                            "    {} {}; /* {} DEFAULT {} */",
1357                            field_type, field_name, tn, dv
1358                        )?;
1359                    } else {
1360                        writeln!(
1361                            output,
1362                            "    {} {}; /* DEFAULT {} */",
1363                            field_type, field_name, dv
1364                        )?;
1365                    }
1366                } else if let Some(ref tn) = tag_note {
1367                    writeln!(output, "    {} {}; /* {} */", field_type, field_name, tn)?;
1368                } else {
1369                    writeln!(output, "    {} {};", field_type, field_name)?;
1370                }
1371            }
1372        }
1373    }
1374
1375    writeln!(output, "}};")?;
1376    Ok(())
1377}
1378
1379/// Get C type for a field (handles embedded structs differently)
1380fn get_c_type_for_field(ty: &Type) -> String {
1381    match ty {
1382        Type::TypeRef(name) => to_pascal_case(name),
1383        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
1384            // Array types - use pointer with length
1385            format!("{}*", get_c_type(inner))
1386        }
1387        _ => get_c_type(ty),
1388    }
1389}
1390
1391/// Generate a struct definition for CHOICE type
1392fn generate_choice_struct(
1393    output: &mut String,
1394    name: &str,
1395    variants: &[ChoiceVariant],
1396) -> Result<(), Box<dyn std::error::Error>> {
1397    let struct_name = to_pascal_case(name);
1398    let tag_enum_name = format!("{}Tag", struct_name);
1399
1400    // Generate tag enum
1401    writeln!(output, "typedef enum {} {{", tag_enum_name)?;
1402    for (i, variant) in variants.iter().enumerate() {
1403        let variant_name = to_pascal_case(&variant.name);
1404        let comma = if i < variants.len() - 1 { "," } else { "" };
1405        writeln!(output, "    {}_{}{}", tag_enum_name, variant_name, comma)?;
1406    }
1407    writeln!(output, "}} {};", tag_enum_name)?;
1408    writeln!(output)?;
1409
1410    // Generate choice struct
1411    writeln!(output, "struct {} {{", struct_name)?;
1412    writeln!(output, "    {} tag;", tag_enum_name)?;
1413    writeln!(output, "    union {{")?;
1414
1415    for variant in variants {
1416        // Skip NULL types as they don't have data
1417        if matches!(variant.ty, Type::Null) {
1418            continue;
1419        }
1420
1421        let variant_name = to_snake_case(&variant.name);
1422        // Inline anonymous SEQUENCE/SET cannot be embedded by value in a C union;
1423        // emit a void* pointer with a comment so callers can cast to the right type.
1424        if matches!(peel_type(&variant.ty), Type::Sequence(_) | Type::Set(_)) {
1425            writeln!(
1426                output,
1427                "        void* {}; /* inline SEQUENCE/SET — define separately and cast */",
1428                variant_name
1429            )?;
1430        } else {
1431            let variant_type = get_c_type_for_field(&variant.ty);
1432            writeln!(output, "        {} {};", variant_type, variant_name)?;
1433        }
1434    }
1435
1436    writeln!(output, "    }} value;")?;
1437    writeln!(output, "}};")?;
1438    Ok(())
1439}
1440
1441/// Generate enum for INTEGER with named numbers
1442fn generate_enum_for_integer(
1443    output: &mut String,
1444    name: &str,
1445    named_numbers: &[NamedNumber],
1446) -> Result<(), Box<dyn std::error::Error>> {
1447    let enum_name = to_pascal_case(name);
1448    writeln!(output, "enum {} {{", enum_name)?;
1449
1450    for (i, nn) in named_numbers.iter().enumerate() {
1451        let variant_name = to_screaming_snake_case(&nn.name);
1452        let comma = if i < named_numbers.len() - 1 { "," } else { "" };
1453        writeln!(
1454            output,
1455            "    {}_{} = {}{}",
1456            enum_name, variant_name, nn.value, comma
1457        )?;
1458    }
1459
1460    writeln!(output, "}};")?;
1461    Ok(())
1462}
1463
1464/// Generate `#define` constants for an unconstrained INTEGER with named numbers.
1465///
1466/// # Why `int64_t`?
1467///
1468/// ASN.1 INTEGER is an unbounded type.  Named numbers (`{ tcp(6), udp(17) }`)
1469/// are symbolic labels for specific values; the actual DER-encoded value on
1470/// the wire may be any valid INTEGER, not just the listed names.  Using
1471/// `int64_t` matches what the runtime's `synta_integer_to_i64` returns, so
1472/// the decode/encode round-trip is correct over the full representable range
1473/// without truncation.
1474///
1475/// If a bounded representation is required, the schema should carry an
1476/// explicit size constraint (`INTEGER (0..65535)`), which causes the
1477/// constrained-integer path (`generate_constrained_integer_c`) to be taken
1478/// instead — that path wraps the value in a validated newtype struct.
1479///
1480/// # Output
1481///
1482/// The forward-declarations section already emits `typedef int64_t TypeName;`.
1483/// This function emits the accompanying preprocessor constants:
1484///
1485/// ```c
1486/// /* Named integer values for Protocol */
1487/// #define PROTOCOL_TCP  ((int64_t)6)
1488/// #define PROTOCOL_UDP  ((int64_t)17)
1489/// ```
1490///
1491/// Macro names are padded to the same length for visual alignment.
1492fn generate_defines_for_integer(
1493    output: &mut String,
1494    name: &str,
1495    named_numbers: &[NamedNumber],
1496) -> Result<(), Box<dyn std::error::Error>> {
1497    let type_name = to_pascal_case(name);
1498    let prefix = to_screaming_snake_case(name);
1499
1500    writeln!(output, "/* Named integer values for {} */", type_name)?;
1501
1502    // Pre-compute macro names to allow padding for alignment.
1503    let macro_names: Vec<String> = named_numbers
1504        .iter()
1505        .map(|nn| format!("{}_{}", prefix, to_snake_case(&nn.name).to_uppercase()))
1506        .collect();
1507    let max_len = macro_names.iter().map(|s| s.len()).max().unwrap_or(0);
1508
1509    for (nn, macro_name) in named_numbers.iter().zip(&macro_names) {
1510        writeln!(
1511            output,
1512            "#define {:width$} ((int64_t){})",
1513            macro_name,
1514            nn.value,
1515            width = max_len
1516        )?;
1517    }
1518
1519    Ok(())
1520}
1521
1522/// Generate encoder/decoder function prototypes
1523fn generate_encoder_decoder_prototypes(
1524    output: &mut String,
1525    def: &Definition,
1526    arena_mode: bool,
1527) -> Result<(), Box<dyn std::error::Error>> {
1528    let c_name = to_pascal_case(&def.name);
1529    let fn_prefix = to_snake_case(&def.name);
1530
1531    // Decoder prototype
1532    writeln!(
1533        output,
1534        "SyntaErrorCode {}_decode(SyntaDecoder* decoder, {}* out);",
1535        fn_prefix, c_name
1536    )?;
1537
1538    // Arena decoder prototype
1539    if arena_mode {
1540        writeln!(
1541            output,
1542            "SyntaErrorCode {}_decode_arena(SyntaDecoder* decoder, SyntaArena* arena, {}* out);",
1543            fn_prefix, c_name
1544        )?;
1545    }
1546
1547    // Encoder prototype
1548    writeln!(
1549        output,
1550        "SyntaErrorCode {}_encode(SyntaEncoder* encoder, const {}* value);",
1551        fn_prefix, c_name
1552    )?;
1553
1554    // Free function for complex types
1555    match &def.ty {
1556        Type::Sequence(_)
1557        | Type::Set(_)
1558        | Type::Choice(_)
1559        | Type::SequenceOf(_, _)
1560        | Type::SetOf(_, _) => {
1561            writeln!(output, "void {}_free({}* value);", fn_prefix, c_name)?;
1562        }
1563        _ => {}
1564    }
1565
1566    // Default constructor for sequences where every field is OPTIONAL or has a DEFAULT
1567    if let Type::Sequence(fields) | Type::Set(fields) = peel_type(&def.ty) {
1568        if fields.iter().all(|f| f.optional || f.default.is_some()) {
1569            writeln!(output, "{} {}_default(void);", c_name, fn_prefix)?;
1570        }
1571    }
1572
1573    Ok(())
1574}
1575
1576/// Generate helper functions (init, validate, print) for a definition.
1577fn generate_helper_functions(
1578    output: &mut String,
1579    def: &Definition,
1580) -> Result<(), Box<dyn std::error::Error>> {
1581    let c_name = to_pascal_case(&def.name);
1582    let fn_prefix = to_snake_case(&def.name);
1583
1584    match &def.ty {
1585        Type::Sequence(fields) | Type::Set(fields) => {
1586            generate_init_helper(output, &c_name, &fn_prefix)?;
1587            writeln!(output)?;
1588            generate_validate_sequence(output, &c_name, &fn_prefix, fields)?;
1589            writeln!(output)?;
1590            generate_print_sequence(output, &c_name, &fn_prefix, fields)?;
1591        }
1592        Type::Choice(variants) => {
1593            generate_validate_choice(output, &c_name, &fn_prefix, variants)?;
1594            writeln!(output)?;
1595            generate_print_choice(output, &c_name, &fn_prefix, variants)?;
1596        }
1597        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
1598            generate_init_helper(output, &c_name, &fn_prefix)?;
1599            writeln!(output)?;
1600            generate_validate_array(output, &c_name, &fn_prefix)?;
1601            writeln!(output)?;
1602            generate_print_array(output, &c_name, &fn_prefix, inner)?;
1603        }
1604        _ => {}
1605    }
1606
1607    Ok(())
1608}
1609
1610/// Emit a `static inline void <prefix>_init(<Name>* value)` zero-initialiser.
1611///
1612/// Uses `memset` to zero the entire struct so that optional `has_*` flags
1613/// start as `false` and all pointer fields start as `NULL`.
1614fn generate_init_helper(
1615    output: &mut String,
1616    c_name: &str,
1617    fn_prefix: &str,
1618) -> Result<(), Box<dyn std::error::Error>> {
1619    writeln!(
1620        output,
1621        "static inline void {}_init({}* value) {{",
1622        fn_prefix, c_name
1623    )?;
1624    writeln!(output, "    if (value != NULL) {{")?;
1625    writeln!(output, "        memset(value, 0, sizeof({}));", c_name)?;
1626    writeln!(output, "    }}")?;
1627    writeln!(output, "}}")?;
1628    Ok(())
1629}
1630
1631/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1632/// helper for a SEQUENCE or SET struct.
1633///
1634/// Required pointer fields are checked for non-`NULL`; optional pointer fields
1635/// are only checked when their `has_*` flag is `true`.  `NULL` fields return
1636/// `SyntaErrorCode_InvalidArgument`; value types (BOOLEAN, REAL, C enums, etc.)
1637/// are not checked.  Returns `SyntaErrorCode_Success` when all checks pass.
1638fn generate_validate_sequence(
1639    output: &mut String,
1640    c_name: &str,
1641    fn_prefix: &str,
1642    fields: &[SequenceField],
1643) -> Result<(), Box<dyn std::error::Error>> {
1644    writeln!(
1645        output,
1646        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1647        fn_prefix, c_name
1648    )?;
1649    writeln!(
1650        output,
1651        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1652    )?;
1653    for field in fields {
1654        if matches!(field.ty, Type::Null) {
1655            continue;
1656        }
1657        let field_name = to_snake_case(&field.name);
1658        if is_pointer_c_type(&field.ty) {
1659            if field.optional {
1660                writeln!(
1661                    output,
1662                    "    if (value->has_{name} && value->{name} == NULL) return SyntaErrorCode_InvalidArgument;",
1663                    name = field_name
1664                )?;
1665            } else {
1666                writeln!(
1667                    output,
1668                    "    if (value->{} == NULL) return SyntaErrorCode_InvalidArgument;",
1669                    field_name
1670                )?;
1671            }
1672        }
1673    }
1674    writeln!(output, "    return SyntaErrorCode_Success;")?;
1675    writeln!(output, "}}")?;
1676    Ok(())
1677}
1678
1679/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1680/// debugging helper for a SEQUENCE or SET struct.
1681///
1682/// Prints each field name and value via `fprintf`.  Optional absent fields are
1683/// printed as `(absent)`.  Requires `<stdio.h>`, which is included automatically
1684/// when `generate_helpers` is enabled in [`CCodeGenConfig`].
1685fn generate_print_sequence(
1686    output: &mut String,
1687    c_name: &str,
1688    fn_prefix: &str,
1689    fields: &[SequenceField],
1690) -> Result<(), Box<dyn std::error::Error>> {
1691    writeln!(
1692        output,
1693        "static inline void {}_print(const {}* value, FILE* stream) {{",
1694        fn_prefix, c_name
1695    )?;
1696    writeln!(
1697        output,
1698        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
1699        c_name
1700    )?;
1701    writeln!(output, "    fprintf(stream, \"{}{{\\n\");", c_name)?;
1702    for field in fields {
1703        if matches!(field.ty, Type::Null) {
1704            continue;
1705        }
1706        let field_name = to_snake_case(&field.name);
1707        write_sequence_field_print(output, &field_name, &field.ty, field.optional)?;
1708    }
1709    writeln!(output, "    fprintf(stream, \"}}\\n\");")?;
1710    writeln!(output, "}}")?;
1711    Ok(())
1712}
1713
1714/// Emit the fprintf statement(s) for one field inside a SEQUENCE print helper.
1715fn write_sequence_field_print(
1716    output: &mut String,
1717    field_name: &str,
1718    ty: &Type,
1719    optional: bool,
1720) -> Result<(), Box<dyn std::error::Error>> {
1721    let base = peel_type(ty);
1722
1723    // SequenceOf/SetOf store a count field separately; no has_ flag even when optional.
1724    if matches!(base, Type::SequenceOf(_, _) | Type::SetOf(_, _)) {
1725        write_field_value_print(
1726            output,
1727            field_name,
1728            base,
1729            &format!("value->{}", field_name),
1730            "    ",
1731        )?;
1732        return Ok(());
1733    }
1734
1735    if optional {
1736        writeln!(output, "    if (value->has_{}) {{", field_name)?;
1737        write_field_value_print(
1738            output,
1739            field_name,
1740            base,
1741            &format!("value->{}", field_name),
1742            "        ",
1743        )?;
1744        writeln!(output, "    }} else {{")?;
1745        writeln!(
1746            output,
1747            "        fprintf(stream, \"  {}: (absent)\\n\");",
1748            field_name
1749        )?;
1750        writeln!(output, "    }}")?;
1751    } else {
1752        write_field_value_print(
1753            output,
1754            field_name,
1755            base,
1756            &format!("value->{}", field_name),
1757            "    ",
1758        )?;
1759    }
1760    Ok(())
1761}
1762
1763/// Emit a fprintf statement for a single field value (type already peeled of Tagged/Constrained).
1764fn write_field_value_print(
1765    output: &mut String,
1766    field_name: &str,
1767    ty: &Type,
1768    field_expr: &str,
1769    indent: &str,
1770) -> Result<(), Box<dyn std::error::Error>> {
1771    match ty {
1772        Type::Boolean => {
1773            writeln!(
1774                output,
1775                "{}fprintf(stream, \"  {}: %s\\n\", {} ? \"true\" : \"false\");",
1776                indent, field_name, field_expr
1777            )?;
1778        }
1779        Type::Real => {
1780            writeln!(
1781                output,
1782                "{}fprintf(stream, \"  {}: %g\\n\", {});",
1783                indent, field_name, field_expr
1784            )?;
1785        }
1786        Type::OctetString(_)
1787        | Type::Utf8String(_)
1788        | Type::PrintableString(_)
1789        | Type::IA5String(_)
1790        | Type::UtcTime
1791        | Type::GeneralizedTime
1792        | Type::Any
1793        | Type::AnyDefinedBy(_) => {
1794            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1795            writeln!(
1796                output,
1797                "{}    fprintf(stream, \"  {}: <string(%zu bytes)>\\n\", synta_octet_string_len({}));",
1798                indent, field_name, field_expr
1799            )?;
1800            writeln!(output, "{}else", indent)?;
1801            writeln!(
1802                output,
1803                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1804                indent, field_name
1805            )?;
1806        }
1807        Type::Integer(_, _) | Type::Enumerated(_) => {
1808            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1809            writeln!(
1810                output,
1811                "{}    fprintf(stream, \"  {}: <integer>\\n\");",
1812                indent, field_name
1813            )?;
1814            writeln!(output, "{}else", indent)?;
1815            writeln!(
1816                output,
1817                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1818                indent, field_name
1819            )?;
1820        }
1821        Type::ObjectIdentifier => {
1822            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1823            writeln!(
1824                output,
1825                "{}    fprintf(stream, \"  {}: <oid>\\n\");",
1826                indent, field_name
1827            )?;
1828            writeln!(output, "{}else", indent)?;
1829            writeln!(
1830                output,
1831                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1832                indent, field_name
1833            )?;
1834        }
1835        Type::BitString(_) => {
1836            writeln!(
1837                output,
1838                "{}fprintf(stream, \"  {}: <bit-string(%u bytes)>\\n\", (unsigned int){}.data.len);",
1839                indent, field_name, field_expr
1840            )?;
1841        }
1842        Type::TypeRef(name) => {
1843            let type_name = to_pascal_case(name);
1844            writeln!(
1845                output,
1846                "{}fprintf(stream, \"  {}: <{}>\\n\");",
1847                indent, field_name, type_name
1848            )?;
1849        }
1850        Type::Sequence(_) | Type::Set(_) => {
1851            writeln!(
1852                output,
1853                "{}fprintf(stream, \"  {}: <struct>\\n\");",
1854                indent, field_name
1855            )?;
1856        }
1857        Type::SequenceOf(_, _) | Type::SetOf(_, _) => {
1858            // field_expr is "value->field_name"; the count field is "value->field_name_count"
1859            writeln!(
1860                output,
1861                "{}fprintf(stream, \"  {}: [%zu elements]\\n\", {}_count);",
1862                indent, field_name, field_expr
1863            )?;
1864        }
1865        Type::Choice(_) => {
1866            writeln!(
1867                output,
1868                "{}fprintf(stream, \"  {}: <choice>\\n\");",
1869                indent, field_name
1870            )?;
1871        }
1872        Type::Null => {}
1873        // Tagged/Constrained are already peeled by the caller.
1874        _ => {
1875            writeln!(
1876                output,
1877                "{}fprintf(stream, \"  {}: <value>\\n\");",
1878                indent, field_name
1879            )?;
1880        }
1881    }
1882    Ok(())
1883}
1884
1885/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1886/// helper for a CHOICE type.
1887///
1888/// Validates the discriminant tag via a `switch` statement.  Any tag value
1889/// not listed in the CHOICE returns `SyntaErrorCode_InvalidEncoding`, preventing
1890/// access to the union through an unrecognised tag.
1891fn generate_validate_choice(
1892    output: &mut String,
1893    c_name: &str,
1894    fn_prefix: &str,
1895    variants: &[ChoiceVariant],
1896) -> Result<(), Box<dyn std::error::Error>> {
1897    let tag_enum = format!("{}Tag", c_name);
1898    writeln!(
1899        output,
1900        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1901        fn_prefix, c_name
1902    )?;
1903    writeln!(
1904        output,
1905        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1906    )?;
1907    writeln!(output, "    switch (value->tag) {{")?;
1908    for variant in variants {
1909        let variant_name = to_pascal_case(&variant.name);
1910        writeln!(output, "        case {}_{}: break;", tag_enum, variant_name)?;
1911    }
1912    writeln!(
1913        output,
1914        "        default: return SyntaErrorCode_InvalidEncoding;"
1915    )?;
1916    writeln!(output, "    }}")?;
1917    writeln!(output, "    return SyntaErrorCode_Success;")?;
1918    writeln!(output, "}}")?;
1919    Ok(())
1920}
1921
1922/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1923/// debugging helper for a CHOICE type.
1924///
1925/// Prints the active variant name via a `switch` on the discriminant tag.
1926/// Unknown tags are printed as `<unknown tag>`.
1927fn generate_print_choice(
1928    output: &mut String,
1929    c_name: &str,
1930    fn_prefix: &str,
1931    variants: &[ChoiceVariant],
1932) -> Result<(), Box<dyn std::error::Error>> {
1933    let tag_enum = format!("{}Tag", c_name);
1934    writeln!(
1935        output,
1936        "static inline void {}_print(const {}* value, FILE* stream) {{",
1937        fn_prefix, c_name
1938    )?;
1939    writeln!(
1940        output,
1941        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
1942        c_name
1943    )?;
1944    writeln!(output, "    switch (value->tag) {{")?;
1945    for variant in variants {
1946        let variant_name = to_pascal_case(&variant.name);
1947        let variant_field = to_snake_case(&variant.name);
1948        writeln!(output, "        case {}_{}:", tag_enum, variant_name)?;
1949        writeln!(
1950            output,
1951            "            fprintf(stream, \"{}{{ {} }}\\n\");",
1952            c_name, variant_field
1953        )?;
1954        writeln!(output, "            break;")?;
1955    }
1956    writeln!(output, "        default:")?;
1957    writeln!(
1958        output,
1959        "            fprintf(stream, \"{}{{ <unknown tag> }}\\n\");",
1960        c_name
1961    )?;
1962    writeln!(output, "            break;")?;
1963    writeln!(output, "    }}")?;
1964    writeln!(output, "}}")?;
1965    Ok(())
1966}
1967
1968/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1969/// helper for a SEQUENCE OF / SET OF type.
1970///
1971/// Checks that `items` is non-`NULL` whenever `count` is non-zero, guarding
1972/// against an inconsistent `{count: n, items: NULL}` state that would cause
1973/// undefined behaviour when the array is iterated.
1974fn generate_validate_array(
1975    output: &mut String,
1976    c_name: &str,
1977    fn_prefix: &str,
1978) -> Result<(), Box<dyn std::error::Error>> {
1979    writeln!(
1980        output,
1981        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1982        fn_prefix, c_name
1983    )?;
1984    writeln!(
1985        output,
1986        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1987    )?;
1988    writeln!(
1989        output,
1990        "    if (value->count > 0 && value->items == NULL) return SyntaErrorCode_InvalidArgument;"
1991    )?;
1992    writeln!(output, "    return SyntaErrorCode_Success;")?;
1993    writeln!(output, "}}")?;
1994    Ok(())
1995}
1996
1997/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1998/// debugging helper for a SEQUENCE OF / SET OF type.
1999///
2000/// Prints the element count.  Individual element values are not printed
2001/// because the element type may itself require a recursive print call; callers
2002/// that need per-element output should iterate and call the element's own
2003/// `_print` helper.
2004fn generate_print_array(
2005    output: &mut String,
2006    c_name: &str,
2007    fn_prefix: &str,
2008    _inner: &Type,
2009) -> Result<(), Box<dyn std::error::Error>> {
2010    writeln!(
2011        output,
2012        "static inline void {}_print(const {}* value, FILE* stream) {{",
2013        fn_prefix, c_name
2014    )?;
2015    writeln!(
2016        output,
2017        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
2018        c_name
2019    )?;
2020    writeln!(
2021        output,
2022        "    fprintf(stream, \"{}[%zu]\\n\", value->count);",
2023        c_name
2024    )?;
2025    writeln!(output, "}}")?;
2026    Ok(())
2027}
2028
2029/// Build a resolved OID registry from the module's value assignments.
2030///
2031/// OIDs that reference other named OIDs (e.g. `id-ori-kem ::= { id-ori 3 }`)
2032/// are resolved iteratively to a flat `Vec<u32>` of arc components.  Unresolvable
2033/// references (imported OID names not present in `values`) are left out of the
2034/// registry and generate a comment in the output instead.
2035fn build_c_oid_registry(values: &[ValueAssignment]) -> std::collections::HashMap<String, Vec<u32>> {
2036    use std::collections::HashMap;
2037    let mut registry: HashMap<String, Vec<u32>> = HashMap::new();
2038
2039    let mut changed = true;
2040    while changed {
2041        changed = false;
2042        for va in values {
2043            if registry.contains_key(&va.name) {
2044                continue;
2045            }
2046            if let Value::ObjectIdentifier(components) = &va.value {
2047                let mut resolved = Vec::new();
2048                let mut can_resolve = true;
2049                for component in components {
2050                    match component {
2051                        OidComponent::Number(n) => resolved.push(*n),
2052                        OidComponent::NamedRef(name) => {
2053                            if let Some(base) = registry.get(name) {
2054                                resolved.extend_from_slice(base);
2055                            } else {
2056                                can_resolve = false;
2057                                break;
2058                            }
2059                        }
2060                    }
2061                }
2062                if can_resolve {
2063                    registry.insert(va.name.clone(), resolved);
2064                    changed = true;
2065                }
2066            }
2067        }
2068    }
2069    registry
2070}
2071
2072/// Escape a string value for use as a C string literal (double-quoted).
2073///
2074/// Replaces backslashes (`\`) and double-quotes (`"`) with their C escape
2075/// sequences.  Other non-ASCII bytes are emitted as `\xNN` hex escapes.
2076fn escape_c_string(s: &str) -> String {
2077    let mut out = String::with_capacity(s.len());
2078    for ch in s.chars() {
2079        match ch {
2080            '\\' => out.push_str("\\\\"),
2081            '"' => out.push_str("\\\""),
2082            '\n' => out.push_str("\\n"),
2083            '\r' => out.push_str("\\r"),
2084            '\t' => out.push_str("\\t"),
2085            c if c.is_ascii() && (c as u8) >= 0x20 && (c as u8) < 0x7f => out.push(c),
2086            c => {
2087                for byte in c.to_string().as_bytes() {
2088                    out.push_str(&format!("\\x{:02x}", byte));
2089                }
2090            }
2091        }
2092    }
2093    out
2094}
2095
2096/// Emit C constant definitions for all value assignments in the module.
2097///
2098/// | ASN.1 value type  | C output                                                 |
2099/// |-------------------|----------------------------------------------------------|
2100/// | OBJECT IDENTIFIER | `static const uint32_t NAME[] = {...};`                  |
2101/// |                   | `#define NAME_LEN n`                                     |
2102/// | INTEGER           | `#define NAME ((int64_t)n)`                              |
2103/// | BOOLEAN           | `#define NAME (true)` / `#define NAME (false)`           |
2104/// | String            | `#define NAME "text"`                                    |
2105///
2106/// OID values that reference other named OIDs in the same module are resolved
2107/// to their full arc component sequence before being emitted.  References that
2108/// cannot be resolved (e.g. imported OID names) produce a comment instead.
2109fn generate_value_constants(
2110    output: &mut String,
2111    module: &Module,
2112) -> Result<(), Box<dyn std::error::Error>> {
2113    if module.values.is_empty() {
2114        return Ok(());
2115    }
2116
2117    writeln!(output, "/* Value constants */")?;
2118    writeln!(output)?;
2119
2120    let oid_registry = build_c_oid_registry(&module.values);
2121
2122    for va in &module.values {
2123        let c_name = to_screaming_snake_case(&va.name);
2124        match &va.value {
2125            Value::ObjectIdentifier(_) => {
2126                if let Some(arcs) = oid_registry.get(&va.name) {
2127                    write!(output, "static const uint32_t {}[] = {{", c_name)?;
2128                    for (i, n) in arcs.iter().enumerate() {
2129                        if i > 0 {
2130                            write!(output, ", ")?;
2131                        }
2132                        write!(output, "{}", n)?;
2133                    }
2134                    writeln!(output, "}};")?;
2135                    writeln!(output, "#define {}_LEN {}", c_name, arcs.len())?;
2136                    writeln!(output)?;
2137                } else {
2138                    writeln!(
2139                        output,
2140                        "/* OID {} could not be fully resolved (unresolved named reference) */",
2141                        va.name
2142                    )?;
2143                    writeln!(output)?;
2144                }
2145            }
2146            Value::Integer(n) => {
2147                writeln!(output, "#define {} ((int64_t){})", c_name, n)?;
2148                writeln!(output)?;
2149            }
2150            Value::Boolean(b) => {
2151                writeln!(
2152                    output,
2153                    "#define {} ({})",
2154                    c_name,
2155                    if *b { "true" } else { "false" }
2156                )?;
2157                writeln!(output)?;
2158            }
2159            Value::String(s) => {
2160                writeln!(output, "#define {} \"{}\"", c_name, escape_c_string(s))?;
2161                writeln!(output)?;
2162            }
2163        }
2164    }
2165
2166    Ok(())
2167}
2168
2169/// Strip `Tagged` and `Constrained` wrappers to reach the underlying base type.
2170fn peel_type(ty: &Type) -> &Type {
2171    match ty {
2172        Type::Tagged { inner, .. }
2173        | Type::Constrained {
2174            base_type: inner, ..
2175        } => peel_type(inner),
2176        _ => ty,
2177    }
2178}
2179
2180/// Return true if `ty` maps to a C pointer (requires a NULL-check in validate helpers).
2181fn is_pointer_c_type(ty: &Type) -> bool {
2182    matches!(
2183        peel_type(ty),
2184        Type::Integer(_, _)
2185            | Type::Enumerated(_)
2186            | Type::OctetString(_)
2187            | Type::ObjectIdentifier
2188            | Type::Utf8String(_)
2189            | Type::PrintableString(_)
2190            | Type::IA5String(_)
2191            | Type::UtcTime
2192            | Type::GeneralizedTime
2193            | Type::Any
2194            | Type::AnyDefinedBy(_)
2195    )
2196}
2197
2198/// Get the C type for an ASN.1 type
2199pub(crate) fn get_c_type(ty: &Type) -> String {
2200    match ty {
2201        Type::Integer(_, _) => "SyntaInteger*".to_string(),
2202        Type::Enumerated(_) => "SyntaInteger*".to_string(),
2203        Type::Real => "double".to_string(),
2204        Type::Boolean => "bool".to_string(),
2205        Type::OctetString(_) => "SyntaOctetString*".to_string(),
2206        Type::BitString(_) => "SyntaBitString".to_string(),
2207        Type::ObjectIdentifier => "SyntaObjectIdentifier*".to_string(),
2208        Type::Null => "void".to_string(),
2209        Type::Utf8String(_)
2210        | Type::PrintableString(_)
2211        | Type::IA5String(_)
2212        | Type::TeletexString(_)
2213        | Type::UniversalString(_)
2214        | Type::BmpString(_)
2215        | Type::GeneralString(_)
2216        | Type::NumericString(_)
2217        | Type::VisibleString(_) => {
2218            "SyntaOctetString*".to_string() // Use OctetString for strings (FFI may not have typed strings yet)
2219        }
2220        Type::UtcTime | Type::GeneralizedTime => "SyntaOctetString*".to_string(), // Use OctetString for now (FFI may not have Time yet)
2221        Type::Sequence(_) | Type::Set(_) => "struct /* complex type */".to_string(),
2222        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
2223            format!("{}*", get_c_type(inner))
2224        }
2225        Type::Choice(_) => "union /* choice */".to_string(),
2226        Type::TypeRef(name) => to_pascal_case(name),
2227        Type::Tagged { inner, .. } => get_c_type(inner),
2228        Type::Constrained { base_type, .. } => get_c_type(base_type),
2229        Type::Any => "SyntaOctetString*".to_string(), // ANY is encoded as OCTET STRING
2230        Type::AnyDefinedBy(_) => "SyntaOctetString*".to_string(), // ANY DEFINED BY also as OCTET STRING
2231        Type::Class(_) => "void /* class */".to_string(),         // IOC class — no C type
2232    }
2233}
2234
2235#[cfg(test)]
2236mod tests {
2237    use super::*;
2238
2239    #[test]
2240    fn test_generate_simple_sequence() {
2241        let module = Module {
2242            name: "TestModule".to_string(),
2243            oid: None,
2244            values: vec![],
2245            tagging_mode: None,
2246            imports: vec![],
2247            exports: vec![],
2248            definitions: vec![Definition {
2249                name: "SimpleSeq".to_string(),
2250                ty: Type::Sequence(vec![
2251                    SequenceField {
2252                        name: "version".to_string(),
2253                        ty: Type::Integer(None, vec![]),
2254                        optional: false,
2255                        default: None,
2256                    },
2257                    SequenceField {
2258                        name: "serialNumber".to_string(),
2259                        ty: Type::Integer(None, vec![]),
2260                        optional: false,
2261                        default: None,
2262                    },
2263                ]),
2264            }],
2265        };
2266
2267        let result = generate_c(&module).unwrap();
2268        assert!(result.contains("typedef struct SimpleSeq"));
2269        assert!(result.contains("SyntaInteger* version;"));
2270        assert!(result.contains("SyntaInteger* serial_number;"));
2271        assert!(result.contains("simple_seq_decode"));
2272        assert!(result.contains("simple_seq_encode"));
2273    }
2274
2275    #[test]
2276    fn test_generate_choice() {
2277        let module = Module {
2278            name: "TestModule".to_string(),
2279            oid: None,
2280            values: vec![],
2281            tagging_mode: None,
2282            imports: vec![],
2283            exports: vec![],
2284            definitions: vec![Definition {
2285                name: "MyChoice".to_string(),
2286                ty: Type::Choice(vec![
2287                    ChoiceVariant {
2288                        name: "intVal".to_string(),
2289                        ty: Type::Integer(None, vec![]),
2290                    },
2291                    ChoiceVariant {
2292                        name: "boolVal".to_string(),
2293                        ty: Type::Boolean,
2294                    },
2295                ]),
2296            }],
2297        };
2298
2299        let result = generate_c(&module).unwrap();
2300        assert!(result.contains("typedef enum MyChoiceTag"));
2301        assert!(result.contains("typedef struct MyChoice"));
2302        assert!(result.contains("MyChoiceTag tag;"));
2303        assert!(result.contains("union {"));
2304    }
2305
2306    #[test]
2307    fn test_generate_helpers() {
2308        let module = Module {
2309            name: "TestModule".to_string(),
2310            oid: None,
2311            values: vec![],
2312            tagging_mode: None,
2313            imports: vec![],
2314            exports: vec![],
2315            definitions: vec![
2316                Definition {
2317                    name: "SimpleSeq".to_string(),
2318                    ty: Type::Sequence(vec![
2319                        SequenceField {
2320                            name: "version".to_string(),
2321                            ty: Type::Integer(None, vec![]),
2322                            optional: false,
2323                            default: None,
2324                        },
2325                        SequenceField {
2326                            name: "flag".to_string(),
2327                            ty: Type::Boolean,
2328                            optional: true,
2329                            default: None,
2330                        },
2331                    ]),
2332                },
2333                Definition {
2334                    name: "MyChoice".to_string(),
2335                    ty: Type::Choice(vec![
2336                        ChoiceVariant {
2337                            name: "intVal".to_string(),
2338                            ty: Type::Integer(None, vec![]),
2339                        },
2340                        ChoiceVariant {
2341                            name: "boolVal".to_string(),
2342                            ty: Type::Boolean,
2343                        },
2344                    ]),
2345                },
2346            ],
2347        };
2348
2349        let config = CCodeGenConfig {
2350            generate_helpers: true,
2351            ..Default::default()
2352        };
2353        let result = generate_c_with_config(&module, config).unwrap();
2354
2355        // stdio.h included when helpers are on
2356        assert!(result.contains("#include <stdio.h>"));
2357
2358        // _init for SEQUENCE
2359        assert!(result.contains("simple_seq_init"));
2360        // _validate for SEQUENCE
2361        assert!(result.contains("simple_seq_validate"));
2362        assert!(result.contains("SyntaErrorCode_NullPointer"));
2363        // required integer field null-check
2364        assert!(result.contains("value->version == NULL"));
2365        // optional boolean field — no null check (not a pointer type)
2366        assert!(!result.contains("value->flag == NULL"));
2367        // _print for SEQUENCE
2368        assert!(result.contains("simple_seq_print"));
2369        assert!(result.contains("FILE* stream"));
2370
2371        // _validate for CHOICE
2372        assert!(result.contains("my_choice_validate"));
2373        assert!(result.contains("MyChoiceTag_IntVal"));
2374        assert!(result.contains("SyntaErrorCode_InvalidEncoding"));
2375        // _print for CHOICE
2376        assert!(result.contains("my_choice_print"));
2377    }
2378
2379    #[test]
2380    fn test_generate_named_integer() {
2381        let module = Module {
2382            name: "TestModule".to_string(),
2383            oid: None,
2384            values: vec![],
2385            tagging_mode: None,
2386            imports: vec![],
2387            exports: vec![],
2388            definitions: vec![Definition {
2389                name: "Protocol".to_string(),
2390                ty: Type::Integer(
2391                    None,
2392                    vec![
2393                        NamedNumber {
2394                            name: "tcp".to_string(),
2395                            value: 6,
2396                        },
2397                        NamedNumber {
2398                            name: "udp".to_string(),
2399                            value: 17,
2400                        },
2401                    ],
2402                ),
2403            }],
2404        };
2405
2406        let result = generate_c(&module).unwrap();
2407        // Forward declaration uses int64_t, not enum
2408        assert!(result.contains("typedef int64_t Protocol;"));
2409        // Named constants as #define macros
2410        assert!(result.contains("#define PROTOCOL_TCP"));
2411        assert!(result.contains("((int64_t)6)"));
2412        assert!(result.contains("#define PROTOCOL_UDP"));
2413        assert!(result.contains("((int64_t)17)"));
2414        // No C enum should be generated
2415        assert!(!result.contains("enum Protocol {"));
2416    }
2417
2418    // -----------------------------------------------------------------------
2419    // Constrained INTEGER tests
2420    // -----------------------------------------------------------------------
2421
2422    fn make_constrained_integer_module(
2423        type_name: &str,
2424        constraint: SubtypeConstraint,
2425        named_numbers: Vec<NamedNumber>,
2426    ) -> Module {
2427        Module {
2428            name: "TestModule".to_string(),
2429            oid: None,
2430            values: vec![],
2431            tagging_mode: None,
2432            imports: vec![],
2433            exports: vec![],
2434            definitions: vec![Definition {
2435                name: type_name.to_string(),
2436                ty: Type::Constrained {
2437                    base_type: Box::new(Type::Integer(None, named_numbers)),
2438                    constraint: Constraint {
2439                        spec: ConstraintSpec::Subtype(constraint),
2440                        exception: None,
2441                    },
2442                },
2443            }],
2444        }
2445    }
2446
2447    #[test]
2448    fn test_constrained_integer_value_range() {
2449        // Int32 ::= INTEGER (-2147483648..2147483647)
2450        let module = make_constrained_integer_module(
2451            "Int32",
2452            SubtypeConstraint::ValueRange {
2453                min: ConstraintValue::Integer(-2147483648),
2454                max: ConstraintValue::Integer(2147483647),
2455            },
2456            vec![],
2457        );
2458        let result = generate_c(&module).unwrap();
2459
2460        // Struct typedef — range fits int32_t so int32_t is chosen
2461        assert!(
2462            result.contains("typedef struct { int32_t value; } Int32;"),
2463            "missing struct typedef:\n{}",
2464            result
2465        );
2466        // Range displayed in comment
2467        assert!(result.contains("INTEGER (-2147483648..2147483647)"));
2468        // _new validated constructor
2469        assert!(result.contains("int32_new(int32_t v, Int32* out)"));
2470        assert!(result.contains("v >= -2147483648LL") && result.contains("v <= 2147483647LL"));
2471        // _new_unchecked
2472        assert!(result.contains("int32_new_unchecked(int32_t v)"));
2473        // _get accessor
2474        assert!(result.contains("int32_get(const Int32* self)"));
2475        // _validate
2476        assert!(result.contains("int32_validate(const Int32* self)"));
2477        // decode / encode prototypes
2478        assert!(result.contains("int32_decode(SyntaDecoder*"));
2479        assert!(result.contains("int32_encode(SyntaEncoder*"));
2480        // no _free (plain struct, no heap allocation)
2481        assert!(!result.contains("int32_free"));
2482    }
2483
2484    #[test]
2485    fn test_constrained_integer_single_value() {
2486        // PvNo ::= INTEGER (5)
2487        let module = make_constrained_integer_module(
2488            "PvNo",
2489            SubtypeConstraint::SingleValue(ConstraintValue::Integer(5)),
2490            vec![],
2491        );
2492        let result = generate_c(&module).unwrap();
2493
2494        // Single value 5 ≥ 0 → uint8_t
2495        assert!(result.contains("typedef struct { uint8_t value; } PvNo;"));
2496        assert!(result.contains("INTEGER (5)"));
2497        assert!(result.contains("v == 5LL"));
2498        assert!(result.contains("pv_no_new(uint8_t v, PvNo* out)"));
2499        assert!(result.contains("pv_no_validate(const PvNo* self)"));
2500    }
2501
2502    #[test]
2503    fn test_constrained_integer_min_max_unconstrained() {
2504        // UncheckedInt ::= INTEGER (MIN..MAX)  — all int64_t values valid
2505        let module = make_constrained_integer_module(
2506            "UncheckedInt",
2507            SubtypeConstraint::ValueRange {
2508                min: ConstraintValue::Min,
2509                max: ConstraintValue::Max,
2510            },
2511            vec![],
2512        );
2513        let result = generate_c(&module).unwrap();
2514
2515        assert!(result.contains("typedef struct { int64_t value; } UncheckedInt;"));
2516        // Check expression must be "1" (always true) — no bound to check
2517        assert!(result.contains("return 1;"));
2518        // The if(!()) guard must also evaluate to if(!(1)) which is always false, so
2519        // _new always succeeds.
2520        assert!(result.contains("if (!(1)) return false;"));
2521    }
2522
2523    #[test]
2524    fn test_constrained_integer_half_open_range() {
2525        // NonNegInt ::= INTEGER (0..MAX)
2526        let module = make_constrained_integer_module(
2527            "NonNegInt",
2528            SubtypeConstraint::ValueRange {
2529                min: ConstraintValue::Integer(0),
2530                max: ConstraintValue::Max,
2531            },
2532            vec![],
2533        );
2534        let result = generate_c(&module).unwrap();
2535
2536        // Only a lower bound should be generated (no upper bound for MAX)
2537        assert!(result.contains("v >= 0LL"));
2538        assert!(!result.contains("v <= INT64_MAX"));
2539    }
2540
2541    #[test]
2542    fn test_constrained_integer_union() {
2543        // SmallOrLarge ::= INTEGER (0..10 | 100..200)
2544        let module = make_constrained_integer_module(
2545            "SmallOrLarge",
2546            SubtypeConstraint::Union(vec![
2547                SubtypeConstraint::ValueRange {
2548                    min: ConstraintValue::Integer(0),
2549                    max: ConstraintValue::Integer(10),
2550                },
2551                SubtypeConstraint::ValueRange {
2552                    min: ConstraintValue::Integer(100),
2553                    max: ConstraintValue::Integer(200),
2554                },
2555            ]),
2556            vec![],
2557        );
2558        let result = generate_c(&module).unwrap();
2559
2560        assert!(result.contains("typedef struct { int64_t value; } SmallOrLarge;"));
2561        // Union operator
2562        assert!(result.contains("||"));
2563        assert!(result.contains("v >= 0LL") && result.contains("v <= 10LL"));
2564        assert!(result.contains("v >= 100LL") && result.contains("v <= 200LL"));
2565    }
2566
2567    #[test]
2568    fn test_constrained_integer_complement() {
2569        // NotZero ::= INTEGER (ALL EXCEPT 0)
2570        let module = make_constrained_integer_module(
2571            "NotZero",
2572            SubtypeConstraint::Complement(Box::new(SubtypeConstraint::SingleValue(
2573                ConstraintValue::Integer(0),
2574            ))),
2575            vec![],
2576        );
2577        let result = generate_c(&module).unwrap();
2578
2579        assert!(result.contains("typedef struct { int64_t value; } NotZero;"));
2580        assert!(result.contains("!(v == 0LL)"));
2581    }
2582
2583    #[test]
2584    fn test_constrained_integer_with_named_numbers() {
2585        // MsgType ::= INTEGER (0..30) — with named constants
2586        let module = make_constrained_integer_module(
2587            "MsgType",
2588            SubtypeConstraint::ValueRange {
2589                min: ConstraintValue::Integer(0),
2590                max: ConstraintValue::Integer(30),
2591            },
2592            vec![
2593                NamedNumber {
2594                    name: "asReq".to_string(),
2595                    value: 10,
2596                },
2597                NamedNumber {
2598                    name: "asRep".to_string(),
2599                    value: 11,
2600                },
2601            ],
2602        );
2603        let result = generate_c(&module).unwrap();
2604
2605        // Range 0..30 ≥ 0 → uint8_t
2606        assert!(result.contains("typedef struct { uint8_t value; } MsgType;"));
2607        // Named constants emitted as typed #define macros
2608        assert!(result.contains("#define MsgType_AS_REQ ((uint8_t)10)"));
2609        assert!(result.contains("#define MsgType_AS_REP ((uint8_t)11)"));
2610        // Still has validation helpers
2611        assert!(result.contains("msg_type_new(uint8_t v, MsgType* out)"));
2612        assert!(result.contains("msg_type_validate(const MsgType* self)"));
2613    }
2614
2615    #[test]
2616    fn test_format_c_constraint_display() {
2617        assert_eq!(
2618            format_c_constraint_display(&SubtypeConstraint::ValueRange {
2619                min: ConstraintValue::Integer(-128),
2620                max: ConstraintValue::Integer(127),
2621            }),
2622            "-128..127"
2623        );
2624        assert_eq!(
2625            format_c_constraint_display(&SubtypeConstraint::SingleValue(ConstraintValue::Integer(
2626                42
2627            ))),
2628            "42"
2629        );
2630        assert_eq!(
2631            format_c_constraint_display(&SubtypeConstraint::ValueRange {
2632                min: ConstraintValue::Min,
2633                max: ConstraintValue::Max,
2634            }),
2635            "MIN..MAX"
2636        );
2637    }
2638
2639    #[test]
2640    fn test_generate_c_constraint_check() {
2641        // Concrete range, signed type — both bounds emitted
2642        assert_eq!(
2643            generate_c_constraint_check(
2644                "val",
2645                &SubtypeConstraint::ValueRange {
2646                    min: ConstraintValue::Integer(0),
2647                    max: ConstraintValue::Integer(100),
2648                },
2649                "int64_t",
2650            ),
2651            "(val >= 0LL && val <= 100LL)"
2652        );
2653        // Same range, unsigned type — lower bound 0 is trivially satisfied; omitted
2654        assert_eq!(
2655            generate_c_constraint_check(
2656                "val",
2657                &SubtypeConstraint::ValueRange {
2658                    min: ConstraintValue::Integer(0),
2659                    max: ConstraintValue::Integer(100),
2660                },
2661                "uint8_t",
2662            ),
2663            "(val <= 100LL)"
2664        );
2665        // Single value — unsigned does not change equality check
2666        assert_eq!(
2667            generate_c_constraint_check(
2668                "val",
2669                &SubtypeConstraint::SingleValue(ConstraintValue::Integer(5)),
2670                "int64_t",
2671            ),
2672            "val == 5LL"
2673        );
2674        // MIN..MAX — always true regardless of type
2675        assert_eq!(
2676            generate_c_constraint_check(
2677                "val",
2678                &SubtypeConstraint::ValueRange {
2679                    min: ConstraintValue::Min,
2680                    max: ConstraintValue::Max,
2681                },
2682                "int64_t",
2683            ),
2684            "1"
2685        );
2686        // Half-open (lower-bounded only), signed — keep the check
2687        assert_eq!(
2688            generate_c_constraint_check(
2689                "val",
2690                &SubtypeConstraint::ValueRange {
2691                    min: ConstraintValue::Integer(0),
2692                    max: ConstraintValue::Max,
2693                },
2694                "int64_t",
2695            ),
2696            "(val >= 0LL)"
2697        );
2698        // Complement
2699        assert_eq!(
2700            generate_c_constraint_check(
2701                "val",
2702                &SubtypeConstraint::Complement(Box::new(SubtypeConstraint::SingleValue(
2703                    ConstraintValue::Integer(0)
2704                ))),
2705                "int64_t",
2706            ),
2707            "!(val == 0LL)"
2708        );
2709    }
2710
2711    // -----------------------------------------------------------------------
2712    // Constrained string tests
2713    // -----------------------------------------------------------------------
2714
2715    fn make_constrained_string_module(
2716        type_name: &str,
2717        base_ty: Type,
2718        constraint: SubtypeConstraint,
2719    ) -> Module {
2720        Module {
2721            name: "TestModule".to_string(),
2722            oid: None,
2723            values: vec![],
2724            tagging_mode: None,
2725            imports: vec![],
2726            exports: vec![],
2727            definitions: vec![Definition {
2728                name: type_name.to_string(),
2729                ty: Type::Constrained {
2730                    base_type: Box::new(base_ty),
2731                    constraint: Constraint {
2732                        spec: ConstraintSpec::Subtype(constraint),
2733                        exception: None,
2734                    },
2735                },
2736            }],
2737        }
2738    }
2739
2740    #[test]
2741    fn test_constrained_string_size_only() {
2742        // Realm ::= IA5String (SIZE (1..MAX))
2743        let module = make_constrained_string_module(
2744            "Realm",
2745            Type::IA5String(None),
2746            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2747                min: ConstraintValue::Integer(1),
2748                max: ConstraintValue::Max,
2749            })),
2750        );
2751        let result = generate_c(&module).unwrap();
2752
2753        // Struct typedef
2754        assert!(
2755            result.contains("typedef struct { SyntaByteArray value; } Realm;"),
2756            "missing struct typedef:\n{}",
2757            result
2758        );
2759        // Comment shows base type and constraint
2760        assert!(result.contains("/* IA5String (SIZE (1..MAX)) */"));
2761        // _new validated constructor
2762        assert!(result.contains("realm_new(SyntaByteArray value, Realm* out)"));
2763        assert!(result.contains("uint32_t _len = value.len;"));
2764        assert!(result.contains("_len >= 1U"));
2765        // _new_unchecked
2766        assert!(result.contains("realm_new_unchecked(SyntaByteArray value)"));
2767        // _get accessor
2768        assert!(result.contains("realm_get(const Realm* self)"));
2769        assert!(result.contains("r.owned = 0"));
2770        // _validate
2771        assert!(result.contains("realm_validate(const Realm* self)"));
2772        assert!(result.contains("uint32_t _len = self->value.len;"));
2773        // _free
2774        assert!(result.contains("realm_free(Realm* self)"));
2775        assert!(result.contains("synta_byte_array_free"));
2776        // decode / encode prototypes
2777        assert!(result.contains("realm_decode(SyntaDecoder*"));
2778        assert!(result.contains("realm_encode(SyntaEncoder*"));
2779    }
2780
2781    #[test]
2782    fn test_constrained_string_size_exact() {
2783        // FixedTag ::= OCTET STRING (SIZE (4))
2784        let module = make_constrained_string_module(
2785            "FixedTag",
2786            Type::OctetString(None),
2787            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::SingleValue(
2788                ConstraintValue::Integer(4),
2789            ))),
2790        );
2791        let result = generate_c(&module).unwrap();
2792
2793        assert!(result.contains("/* OCTET STRING (SIZE (4)) */"));
2794        assert!(result.contains("typedef struct { SyntaByteArray value; } FixedTag;"));
2795        assert!(result.contains("_len == 4U"));
2796        assert!(result.contains("fixed_tag_new(SyntaByteArray value, FixedTag* out)"));
2797        assert!(result.contains("fixed_tag_validate(const FixedTag* self)"));
2798        assert!(result.contains("fixed_tag_free(FixedTag* self)"));
2799    }
2800
2801    #[test]
2802    fn test_constrained_string_size_zero_min() {
2803        // OptStr ::= IA5String (SIZE (0..255))  — no lower bound check needed (uint32_t >= 0 always)
2804        let module = make_constrained_string_module(
2805            "OptStr",
2806            Type::IA5String(None),
2807            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2808                min: ConstraintValue::Integer(0),
2809                max: ConstraintValue::Integer(255),
2810            })),
2811        );
2812        let result = generate_c(&module).unwrap();
2813
2814        // Upper bound present
2815        assert!(result.contains("_len <= 255U"));
2816        // Lower bound 0 is always true for uint32_t — must NOT be emitted
2817        assert!(!result.contains("_len >= 0U"));
2818    }
2819
2820    #[test]
2821    fn test_constrained_string_min_max_size() {
2822        // AnyStr ::= IA5String (SIZE (MIN..MAX)) — always valid
2823        let module = make_constrained_string_module(
2824            "AnyStr",
2825            Type::IA5String(None),
2826            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2827                min: ConstraintValue::Min,
2828                max: ConstraintValue::Max,
2829            })),
2830        );
2831        let result = generate_c(&module).unwrap();
2832
2833        // Should produce "if (!(1)) return false;" — always passes
2834        assert!(result.contains("if (!(1)) return false;"));
2835    }
2836
2837    #[test]
2838    fn test_constrained_string_alphabet_only() {
2839        // DigitStr ::= IA5String (FROM ("0".."9"))
2840        let module = make_constrained_string_module(
2841            "DigitStr",
2842            Type::IA5String(None),
2843            SubtypeConstraint::PermittedAlphabet(vec![CharRange { min: '0', max: '9' }]),
2844        );
2845        let result = generate_c(&module).unwrap();
2846
2847        assert!(result.contains("/* IA5String (FROM (\"0\"..\"9\")) */"));
2848        // Alphabet loop structure
2849        assert!(result.contains("const unsigned char *_ap ="));
2850        assert!(result.contains("unsigned char _c = _ap[_i]"));
2851        assert!(result.contains("_ok = (_c >= '0' && _c <= '9')"));
2852        assert!(result.contains("if (!_ok) return false;"));
2853    }
2854
2855    #[test]
2856    fn test_constrained_string_alphabet_single_char() {
2857        // SingleChar ::= IA5String (FROM ("x"))
2858        let module = make_constrained_string_module(
2859            "SingleChar",
2860            Type::IA5String(None),
2861            SubtypeConstraint::PermittedAlphabet(vec![CharRange { min: 'x', max: 'x' }]),
2862        );
2863        let result = generate_c(&module).unwrap();
2864        // Single char uses == not range
2865        assert!(result.contains("_ok = _c == 'x'"));
2866    }
2867
2868    #[test]
2869    fn test_constrained_string_size_and_alphabet() {
2870        // VisStr ::= PrintableString (SIZE (1..64) FROM ("A".."Z" | "a".."z"))
2871        let module = make_constrained_string_module(
2872            "VisStr",
2873            Type::PrintableString(None),
2874            SubtypeConstraint::Intersection(vec![
2875                SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2876                    min: ConstraintValue::Integer(1),
2877                    max: ConstraintValue::Integer(64),
2878                })),
2879                SubtypeConstraint::PermittedAlphabet(vec![
2880                    CharRange { min: 'A', max: 'Z' },
2881                    CharRange { min: 'a', max: 'z' },
2882                ]),
2883            ]),
2884        );
2885        let result = generate_c(&module).unwrap();
2886
2887        assert!(result.contains("/* PrintableString"));
2888        // SIZE check first
2889        assert!(result.contains("_len >= 1U") && result.contains("_len <= 64U"));
2890        // Alphabet check
2891        assert!(result.contains("(_c >= 'A' && _c <= 'Z') || (_c >= 'a' && _c <= 'z')"));
2892        // _validate also has both checks
2893        assert!(result.contains("uint32_t _len = self->value.len;"));
2894    }
2895
2896    #[test]
2897    fn test_constrained_string_pattern_placeholder() {
2898        // PatStr ::= IA5String (PATTERN "[0-9]+")
2899        let module = make_constrained_string_module(
2900            "PatStr",
2901            Type::IA5String(None),
2902            SubtypeConstraint::Pattern("[0-9]+".to_string()),
2903        );
2904        let result = generate_c(&module).unwrap();
2905
2906        assert!(result.contains("PATTERN constraint \"[0-9]+\" not enforced at runtime"));
2907        // Still emits the struct and helpers
2908        assert!(result.contains("typedef struct { SyntaByteArray value; } PatStr;"));
2909        assert!(result.contains("pat_str_new(SyntaByteArray value, PatStr* out)"));
2910    }
2911
2912    #[test]
2913    fn test_constrained_utf8string() {
2914        // Label ::= UTF8String (SIZE (1..255))
2915        let module = make_constrained_string_module(
2916            "Label",
2917            Type::Utf8String(None),
2918            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2919                min: ConstraintValue::Integer(1),
2920                max: ConstraintValue::Integer(255),
2921            })),
2922        );
2923        let result = generate_c(&module).unwrap();
2924
2925        assert!(result.contains("/* UTF8String (SIZE (1..255)) */"));
2926        assert!(result.contains("typedef struct { SyntaByteArray value; } Label;"));
2927        assert!(result.contains("label_new(SyntaByteArray value, Label* out)"));
2928        assert!(result.contains("label_free(Label* self)"));
2929    }
2930
2931    #[test]
2932    fn test_generate_c_length_check() {
2933        // Exact length
2934        assert_eq!(
2935            generate_c_length_check(
2936                "_len",
2937                &SubtypeConstraint::SingleValue(ConstraintValue::Integer(4))
2938            ),
2939            "_len == 4U"
2940        );
2941        // Range 1..MAX — only lower bound
2942        assert_eq!(
2943            generate_c_length_check(
2944                "_len",
2945                &SubtypeConstraint::ValueRange {
2946                    min: ConstraintValue::Integer(1),
2947                    max: ConstraintValue::Max,
2948                }
2949            ),
2950            "(_len >= 1U)"
2951        );
2952        // Range 0..256 — lower bound 0 omitted
2953        assert_eq!(
2954            generate_c_length_check(
2955                "_len",
2956                &SubtypeConstraint::ValueRange {
2957                    min: ConstraintValue::Integer(0),
2958                    max: ConstraintValue::Integer(256),
2959                }
2960            ),
2961            "(_len <= 256U)"
2962        );
2963        // MIN..MAX — always true
2964        assert_eq!(
2965            generate_c_length_check(
2966                "_len",
2967                &SubtypeConstraint::ValueRange {
2968                    min: ConstraintValue::Min,
2969                    max: ConstraintValue::Max,
2970                }
2971            ),
2972            "1"
2973        );
2974    }
2975
2976    #[test]
2977    fn test_format_c_char_literal() {
2978        assert_eq!(format_c_char_literal('A'), "'A'");
2979        assert_eq!(format_c_char_literal('0'), "'0'");
2980        assert_eq!(format_c_char_literal('\''), "'\\''");
2981        assert_eq!(format_c_char_literal('\\'), "'\\\\'");
2982        assert_eq!(format_c_char_literal('\x01'), "'\\x01'");
2983    }
2984
2985    // -----------------------------------------------------------------------
2986    // Named-bit BIT STRING constants — Task 7
2987    // -----------------------------------------------------------------------
2988
2989    fn make_named_bit_module(name: &str, bits: Vec<NamedNumber>) -> Module {
2990        Module {
2991            name: "TestModule".to_string(),
2992            oid: None,
2993            values: vec![],
2994            tagging_mode: None,
2995            imports: vec![],
2996            exports: vec![],
2997            definitions: vec![Definition {
2998                name: name.to_string(),
2999                ty: Type::Constrained {
3000                    base_type: Box::new(Type::BitString(None)),
3001                    constraint: Constraint {
3002                        spec: ConstraintSpec::Subtype(SubtypeConstraint::NamedBitList(bits)),
3003                        exception: None,
3004                    },
3005                },
3006            }],
3007        }
3008    }
3009
3010    #[test]
3011    fn test_named_bit_string_typedef_and_defines() {
3012        // TicketFlags ::= BIT STRING { reserved(0), forwardable(1), proxiable(3) }
3013        let module = make_named_bit_module(
3014            "TicketFlags",
3015            vec![
3016                NamedNumber {
3017                    name: "reserved".to_string(),
3018                    value: 0,
3019                },
3020                NamedNumber {
3021                    name: "forwardable".to_string(),
3022                    value: 1,
3023                },
3024                NamedNumber {
3025                    name: "proxiable".to_string(),
3026                    value: 3,
3027                },
3028            ],
3029        );
3030        let result = generate_c(&module).unwrap();
3031
3032        // Typedef
3033        assert!(
3034            result.contains("typedef SyntaBitString TicketFlags;"),
3035            "typedef present"
3036        );
3037        // Named bit defines: prefix is TICKET_FLAGS (snake_case of TicketFlags)
3038        assert!(
3039            result.contains("TICKET_FLAGS_RESERVED_BIT"),
3040            "reserved bit define"
3041        );
3042        assert!(
3043            result.contains("TICKET_FLAGS_FORWARDABLE_BIT"),
3044            "forwardable bit define"
3045        );
3046        assert!(
3047            result.contains("TICKET_FLAGS_PROXIABLE_BIT"),
3048            "proxiable bit define"
3049        );
3050        // Values
3051        assert!(
3052            result.contains("TICKET_FLAGS_RESERVED_BIT") && result.contains(" 0"),
3053            "value 0 present"
3054        );
3055        assert!(result.contains(" 1"), "value 1 present");
3056        assert!(result.contains(" 3"), "value 3 present");
3057        // No helper macros without --with-helpers
3058        assert!(!result.contains("IS_SET"), "no IS_SET without helpers");
3059    }
3060
3061    #[test]
3062    fn test_named_bit_string_hyphenated_name() {
3063        // kdc-options ::= BIT STRING { reserved(0), forwardable(1) }
3064        let module = make_named_bit_module(
3065            "kdc-options",
3066            vec![
3067                NamedNumber {
3068                    name: "reserved".to_string(),
3069                    value: 0,
3070                },
3071                NamedNumber {
3072                    name: "forwardable".to_string(),
3073                    value: 1,
3074                },
3075            ],
3076        );
3077        let result = generate_c(&module).unwrap();
3078        // Hyphenated name → PascalCase typedef, SCREAMING prefix with underscore
3079        assert!(
3080            result.contains("typedef SyntaBitString KdcOptions;"),
3081            "typedef with PascalCase"
3082        );
3083        assert!(
3084            result.contains("KDC_OPTIONS_RESERVED_BIT"),
3085            "hyphenated prefix uses underscore"
3086        );
3087        assert!(
3088            result.contains("KDC_OPTIONS_FORWARDABLE_BIT"),
3089            "forwardable bit define"
3090        );
3091    }
3092
3093    #[test]
3094    fn test_named_bit_string_camel_case_bit_name() {
3095        // digitalSignature(0) bit name should become DIGITAL_SIGNATURE
3096        let module = make_named_bit_module(
3097            "KeyUsage",
3098            vec![
3099                NamedNumber {
3100                    name: "digitalSignature".to_string(),
3101                    value: 0,
3102                },
3103                NamedNumber {
3104                    name: "nonRepudiation".to_string(),
3105                    value: 1,
3106                },
3107            ],
3108        );
3109        let result = generate_c(&module).unwrap();
3110        // KeyUsage → KEY_USAGE prefix; camelCase bit names → SCREAMING_SNAKE
3111        assert!(
3112            result.contains("KEY_USAGE_DIGITAL_SIGNATURE_BIT"),
3113            "camelCase bit → SCREAMING_SNAKE"
3114        );
3115        assert!(
3116            result.contains("KEY_USAGE_NON_REPUDIATION_BIT"),
3117            "nonRepudiation bit"
3118        );
3119    }
3120
3121    #[test]
3122    fn test_named_bit_string_with_helpers() {
3123        let module = make_named_bit_module(
3124            "TicketFlags",
3125            vec![NamedNumber {
3126                name: "forwardable".to_string(),
3127                value: 1,
3128            }],
3129        );
3130        let config = CCodeGenConfig {
3131            generate_helpers: true,
3132            ..Default::default()
3133        };
3134        let result = generate_c_with_config(&module, config).unwrap();
3135        // Helper macros present (prefix TICKET_FLAGS from snake_case of TicketFlags)
3136        assert!(
3137            result.contains("TICKET_FLAGS_IS_SET(bs, bit)"),
3138            "IS_SET helper"
3139        );
3140        assert!(result.contains("TICKET_FLAGS_SET(bs, bit)"), "SET helper");
3141        assert!(
3142            result.contains("TICKET_FLAGS_CLEAR(bs, bit)"),
3143            "CLEAR helper"
3144        );
3145        // Helper macros reference the C API functions
3146        assert!(result.contains("synta_bitstring_is_set"), "is_set API call");
3147        assert!(result.contains("synta_bitstring_set"), "set API call");
3148        assert!(result.contains("synta_bitstring_clear"), "clear API call");
3149    }
3150
3151    #[test]
3152    fn test_named_bit_string_empty_list() {
3153        // BIT STRING with empty NamedBitList — just a typedef, no defines
3154        let module = make_named_bit_module("EmptyFlags", vec![]);
3155        let result = generate_c(&module).unwrap();
3156        assert!(
3157            result.contains("typedef SyntaBitString EmptyFlags;"),
3158            "typedef present"
3159        );
3160        assert!(
3161            !result.contains("EMPTY_FLAGS_"),
3162            "no defines for empty list"
3163        );
3164    }
3165
3166    // -----------------------------------------------------------------------
3167    // Named-bit BIT STRING + SIZE constraint — Task 12
3168    //
3169    // BIT STRING { bits } (SIZE N..MAX) is parsed as
3170    //   Intersection([NamedBitList(bits), SizeConstraint(...)]).
3171    // The C codegen must extract the NamedBitList and generate typedef + #defines,
3172    // not fall through to generate_constrained_string_c.
3173    // -----------------------------------------------------------------------
3174
3175    fn make_named_bit_with_size_module(name: &str, bits: Vec<NamedNumber>) -> Module {
3176        // Produces BIT STRING { bits } (SIZE (32..MAX))
3177        Module {
3178            name: "TestModule".to_string(),
3179            oid: None,
3180            values: vec![],
3181            tagging_mode: None,
3182            imports: vec![],
3183            exports: vec![],
3184            definitions: vec![Definition {
3185                name: name.to_string(),
3186                ty: Type::Constrained {
3187                    base_type: Box::new(Type::BitString(None)),
3188                    constraint: Constraint {
3189                        spec: ConstraintSpec::Subtype(SubtypeConstraint::Intersection(vec![
3190                            SubtypeConstraint::NamedBitList(bits),
3191                            SubtypeConstraint::SizeConstraint(Box::new(
3192                                SubtypeConstraint::ValueRange {
3193                                    min: ConstraintValue::Integer(32),
3194                                    max: ConstraintValue::Max,
3195                                },
3196                            )),
3197                        ])),
3198                        exception: None,
3199                    },
3200                },
3201            }],
3202        }
3203    }
3204
3205    #[test]
3206    fn test_named_bit_string_with_size_emits_typedef() {
3207        // BIT STRING { bits } (SIZE (32..MAX)) → typedef SyntaBitString, not struct
3208        let module = make_named_bit_with_size_module(
3209            "TicketFlags",
3210            vec![
3211                NamedNumber {
3212                    name: "forwardable".to_string(),
3213                    value: 1,
3214                },
3215                NamedNumber {
3216                    name: "proxiable".to_string(),
3217                    value: 3,
3218                },
3219            ],
3220        );
3221        let result = generate_c(&module).unwrap();
3222        assert!(
3223            result.contains("typedef SyntaBitString TicketFlags;"),
3224            "combined form must still typedef SyntaBitString; got:\n{}",
3225            result
3226        );
3227        assert!(
3228            result.contains("#define TICKET_FLAGS_FORWARDABLE_BIT"),
3229            "FORWARDABLE_BIT define must appear; got:\n{}",
3230            result
3231        );
3232        assert!(
3233            result.contains("#define TICKET_FLAGS_PROXIABLE_BIT"),
3234            "PROXIABLE_BIT define must appear; got:\n{}",
3235            result
3236        );
3237        // Must NOT produce a struct { SyntaByteArray value; }
3238        assert!(
3239            !result.contains("typedef struct { SyntaByteArray value; } TicketFlags;"),
3240            "combined form must not fall through to constrained-string struct; got:\n{}",
3241            result
3242        );
3243    }
3244
3245    #[test]
3246    fn test_named_bit_string_with_size_helpers() {
3247        // Helpers should still be emitted for the combined form when enabled
3248        let module = make_named_bit_with_size_module(
3249            "KdcOptions",
3250            vec![NamedNumber {
3251                name: "forwardable".to_string(),
3252                value: 1,
3253            }],
3254        );
3255        let config = CCodeGenConfig {
3256            generate_helpers: true,
3257            ..Default::default()
3258        };
3259        let result = generate_c_with_config(&module, config).unwrap();
3260        assert!(
3261            result.contains("KDC_OPTIONS_IS_SET(bs, bit)"),
3262            "IS_SET helper must appear; got:\n{}",
3263            result
3264        );
3265    }
3266
3267    // -----------------------------------------------------------------------
3268    // DEFAULT value annotation tests
3269    // -----------------------------------------------------------------------
3270
3271    fn make_default_module(fields: Vec<SequenceField>) -> Module {
3272        Module {
3273            name: "TestModule".to_string(),
3274            oid: None,
3275            values: vec![],
3276            tagging_mode: None,
3277            imports: vec![],
3278            exports: vec![],
3279            definitions: vec![Definition {
3280                name: "Config".to_string(),
3281                ty: Type::Sequence(fields),
3282            }],
3283        }
3284    }
3285
3286    #[test]
3287    fn test_sequence_default_comment_in_struct() {
3288        // Fields with a DEFAULT value get an inline /* DEFAULT … */ comment.
3289        let module = make_default_module(vec![
3290            SequenceField {
3291                name: "port".to_string(),
3292                ty: Type::Integer(None, vec![]),
3293                optional: false,
3294                default: Some("8080".to_string()),
3295            },
3296            SequenceField {
3297                name: "enabled".to_string(),
3298                ty: Type::Boolean,
3299                optional: false,
3300                default: Some("TRUE".to_string()),
3301            },
3302        ]);
3303        let result = generate_c(&module).unwrap();
3304        assert!(
3305            result.contains("SyntaInteger* port; /* DEFAULT 8080 */"),
3306            "integer default comment"
3307        );
3308        assert!(
3309            result.contains("bool enabled; /* DEFAULT TRUE */"),
3310            "boolean default comment"
3311        );
3312    }
3313
3314    #[test]
3315    fn test_sequence_default_prototype_generated() {
3316        // When every field is OPTIONAL or has a DEFAULT, a _default() prototype appears.
3317        let module = make_default_module(vec![SequenceField {
3318            name: "port".to_string(),
3319            ty: Type::Integer(None, vec![]),
3320            optional: false,
3321            default: Some("8080".to_string()),
3322        }]);
3323        let result = generate_c(&module).unwrap();
3324        assert!(
3325            result.contains("Config config_default(void);"),
3326            "default prototype generated"
3327        );
3328    }
3329
3330    #[test]
3331    fn test_sequence_no_default_prototype_for_required_field() {
3332        // A required field with no DEFAULT means no _default() prototype.
3333        let module = make_default_module(vec![SequenceField {
3334            name: "name".to_string(),
3335            ty: Type::Integer(None, vec![]),
3336            optional: false,
3337            default: None,
3338        }]);
3339        let result = generate_c(&module).unwrap();
3340        assert!(
3341            !result.contains("config_default(void)"),
3342            "no prototype for required-only sequence"
3343        );
3344    }
3345
3346    // -----------------------------------------------------------------------
3347    // Tag annotation comment tests
3348    // -----------------------------------------------------------------------
3349
3350    fn make_tagged_seq_module(
3351        field_name: &str,
3352        class: TagClass,
3353        number: u32,
3354        tagging: Tagging,
3355        inner: Type,
3356    ) -> Module {
3357        Module {
3358            name: "TestModule".to_string(),
3359            oid: None,
3360            values: vec![],
3361            tagging_mode: None,
3362            imports: vec![],
3363            exports: vec![],
3364            definitions: vec![Definition {
3365                name: "Msg".to_string(),
3366                ty: Type::Sequence(vec![SequenceField {
3367                    name: field_name.to_string(),
3368                    ty: Type::Tagged {
3369                        tag: TagInfo {
3370                            class,
3371                            number,
3372                            tagging,
3373                        },
3374                        inner: Box::new(inner),
3375                    },
3376                    optional: false,
3377                    default: None,
3378                }]),
3379            }],
3380        }
3381    }
3382
3383    #[test]
3384    fn test_explicit_tag_annotation_in_struct() {
3385        // [0] EXPLICIT INTEGER → comment on struct field
3386        let module = make_tagged_seq_module(
3387            "id",
3388            TagClass::ContextSpecific,
3389            0,
3390            Tagging::Explicit,
3391            Type::Integer(None, vec![]),
3392        );
3393        let result = generate_c(&module).unwrap();
3394        assert!(
3395            result.contains("SyntaInteger* id; /* [0] EXPLICIT */"),
3396            "explicit tag comment missing; got:\n{}",
3397            result
3398        );
3399    }
3400
3401    #[test]
3402    fn test_implicit_tag_annotation_in_struct() {
3403        // [1] IMPLICIT OCTET STRING → comment on struct field
3404        let module = make_tagged_seq_module(
3405            "data",
3406            TagClass::ContextSpecific,
3407            1,
3408            Tagging::Implicit,
3409            Type::OctetString(None),
3410        );
3411        let result = generate_c(&module).unwrap();
3412        assert!(
3413            result.contains("SyntaOctetString* data; /* [1] IMPLICIT */"),
3414            "implicit tag comment missing; got:\n{}",
3415            result
3416        );
3417    }
3418
3419    #[test]
3420    fn test_application_tag_annotation_in_struct() {
3421        // [APPLICATION 2] IMPLICIT INTEGER → comment on struct field
3422        let module = make_tagged_seq_module(
3423            "val",
3424            TagClass::Application,
3425            2,
3426            Tagging::Implicit,
3427            Type::Integer(None, vec![]),
3428        );
3429        let result = generate_c(&module).unwrap();
3430        assert!(
3431            result.contains("SyntaInteger* val; /* [APPLICATION 2] IMPLICIT */"),
3432            "APPLICATION tag comment missing; got:\n{}",
3433            result
3434        );
3435    }
3436
3437    // -----------------------------------------------------------------------
3438    // Value constants (OID arrays, integer/boolean/string defines)
3439    // -----------------------------------------------------------------------
3440
3441    fn make_values_module(values: Vec<crate::ast::ValueAssignment>) -> Module {
3442        Module {
3443            name: "TestModule".to_string(),
3444            oid: None,
3445            values,
3446            tagging_mode: None,
3447            imports: vec![],
3448            exports: vec![],
3449            definitions: vec![],
3450        }
3451    }
3452
3453    #[test]
3454    fn test_oid_value_constant_emitted() {
3455        // id-ori OBJECT IDENTIFIER ::= { 1 2 840 113549 1 9 16 13 }
3456        let module = make_values_module(vec![crate::ast::ValueAssignment {
3457            name: "id-ori".to_string(),
3458            ty: Type::ObjectIdentifier,
3459            value: Value::ObjectIdentifier(vec![
3460                OidComponent::Number(1),
3461                OidComponent::Number(2),
3462                OidComponent::Number(840),
3463                OidComponent::Number(113549),
3464                OidComponent::Number(1),
3465                OidComponent::Number(9),
3466                OidComponent::Number(16),
3467                OidComponent::Number(13),
3468            ]),
3469        }]);
3470        let result = generate_c(&module).unwrap();
3471        assert!(
3472            result.contains("static const uint32_t ID_ORI[] = {1, 2, 840, 113549, 1, 9, 16, 13};"),
3473            "OID array missing:\n{}",
3474            result
3475        );
3476        assert!(
3477            result.contains("#define ID_ORI_LEN 8"),
3478            "_LEN define missing:\n{}",
3479            result
3480        );
3481    }
3482
3483    #[test]
3484    fn test_oid_named_reference_resolved() {
3485        // id-ori       ::= { 1 2 840 113549 1 9 16 13 }
3486        // id-ori-kem   ::= { id-ori 3 }
3487        let module = make_values_module(vec![
3488            crate::ast::ValueAssignment {
3489                name: "id-ori".to_string(),
3490                ty: Type::ObjectIdentifier,
3491                value: Value::ObjectIdentifier(vec![
3492                    OidComponent::Number(1),
3493                    OidComponent::Number(2),
3494                    OidComponent::Number(840),
3495                    OidComponent::Number(113549),
3496                    OidComponent::Number(1),
3497                    OidComponent::Number(9),
3498                    OidComponent::Number(16),
3499                    OidComponent::Number(13),
3500                ]),
3501            },
3502            crate::ast::ValueAssignment {
3503                name: "id-ori-kem".to_string(),
3504                ty: Type::ObjectIdentifier,
3505                value: Value::ObjectIdentifier(vec![
3506                    OidComponent::NamedRef("id-ori".to_string()),
3507                    OidComponent::Number(3),
3508                ]),
3509            },
3510        ]);
3511        let result = generate_c(&module).unwrap();
3512        assert!(
3513            result.contains(
3514                "static const uint32_t ID_ORI_KEM[] = {1, 2, 840, 113549, 1, 9, 16, 13, 3};"
3515            ),
3516            "resolved child OID missing:\n{}",
3517            result
3518        );
3519        assert!(
3520            result.contains("#define ID_ORI_KEM_LEN 9"),
3521            "_LEN for child OID missing:\n{}",
3522            result
3523        );
3524    }
3525
3526    #[test]
3527    fn test_oid_unresolvable_named_ref_emits_comment() {
3528        // An OID that references an undefined name should emit a comment, not a crash.
3529        let module = make_values_module(vec![crate::ast::ValueAssignment {
3530            name: "my-oid".to_string(),
3531            ty: Type::ObjectIdentifier,
3532            value: Value::ObjectIdentifier(vec![
3533                OidComponent::NamedRef("undefined-base".to_string()),
3534                OidComponent::Number(1),
3535            ]),
3536        }]);
3537        let result = generate_c(&module).unwrap();
3538        assert!(
3539            result.contains("could not be fully resolved"),
3540            "unresolvable OID should produce a comment:\n{}",
3541            result
3542        );
3543        // Must not crash or produce a broken array
3544        assert!(
3545            !result.contains("static const uint32_t MY_OID[] ="),
3546            "broken array must not be emitted:\n{}",
3547            result
3548        );
3549    }
3550
3551    #[test]
3552    fn test_integer_value_constant() {
3553        let module = make_values_module(vec![crate::ast::ValueAssignment {
3554            name: "max-count".to_string(),
3555            ty: Type::Integer(None, vec![]),
3556            value: Value::Integer(256),
3557        }]);
3558        let result = generate_c(&module).unwrap();
3559        assert!(
3560            result.contains("#define MAX_COUNT ((int64_t)256)"),
3561            "integer constant missing:\n{}",
3562            result
3563        );
3564    }
3565
3566    #[test]
3567    fn test_boolean_value_constant() {
3568        let module = make_values_module(vec![
3569            crate::ast::ValueAssignment {
3570                name: "flag-true".to_string(),
3571                ty: Type::Boolean,
3572                value: Value::Boolean(true),
3573            },
3574            crate::ast::ValueAssignment {
3575                name: "flag-false".to_string(),
3576                ty: Type::Boolean,
3577                value: Value::Boolean(false),
3578            },
3579        ]);
3580        let result = generate_c(&module).unwrap();
3581        assert!(
3582            result.contains("#define FLAG_TRUE (true)"),
3583            "true constant missing:\n{}",
3584            result
3585        );
3586        assert!(
3587            result.contains("#define FLAG_FALSE (false)"),
3588            "false constant missing:\n{}",
3589            result
3590        );
3591    }
3592
3593    #[test]
3594    fn test_string_value_constant() {
3595        let module = make_values_module(vec![crate::ast::ValueAssignment {
3596            name: "default-realm".to_string(),
3597            ty: Type::Utf8String(None),
3598            value: Value::String("EXAMPLE.COM".to_string()),
3599        }]);
3600        let result = generate_c(&module).unwrap();
3601        assert!(
3602            result.contains("#define DEFAULT_REALM \"EXAMPLE.COM\""),
3603            "string constant missing:\n{}",
3604            result
3605        );
3606    }
3607
3608    #[test]
3609    fn test_string_escape_in_constant() {
3610        let module = make_values_module(vec![crate::ast::ValueAssignment {
3611            name: "path".to_string(),
3612            ty: Type::Utf8String(None),
3613            value: Value::String("C:\\foo\\bar".to_string()),
3614        }]);
3615        let result = generate_c(&module).unwrap();
3616        assert!(
3617            result.contains("#define PATH \"C:\\\\foo\\\\bar\""),
3618            "backslash not escaped:\n{}",
3619            result
3620        );
3621    }
3622
3623    #[test]
3624    fn test_value_constants_section_header() {
3625        // When there are value assignments, the "Value constants" comment must appear.
3626        let module = make_values_module(vec![crate::ast::ValueAssignment {
3627            name: "x".to_string(),
3628            ty: Type::Integer(None, vec![]),
3629            value: Value::Integer(1),
3630        }]);
3631        let result = generate_c(&module).unwrap();
3632        assert!(
3633            result.contains("/* Value constants */"),
3634            "section comment missing"
3635        );
3636    }
3637
3638    #[test]
3639    fn test_no_value_constants_section_when_empty() {
3640        // When there are no value assignments, no "Value constants" comment.
3641        let module = make_values_module(vec![]);
3642        let result = generate_c(&module).unwrap();
3643        assert!(
3644            !result.contains("/* Value constants */"),
3645            "spurious section comment"
3646        );
3647    }
3648
3649    // -----------------------------------------------------------------------
3650    // Import #include generation
3651    // -----------------------------------------------------------------------
3652
3653    fn make_import_module(imports: Vec<Import>) -> Module {
3654        Module {
3655            name: "TestModule".to_string(),
3656            oid: None,
3657            values: vec![],
3658            tagging_mode: None,
3659            imports,
3660            exports: vec![],
3661            definitions: vec![],
3662        }
3663    }
3664
3665    #[test]
3666    fn test_import_generates_include() {
3667        // IMPORTS AlgorithmIdentifier FROM AlgorithmInformation-2009
3668        let module = make_import_module(vec![Import {
3669            symbols: vec!["AlgorithmIdentifier".to_string()],
3670            module_name: "AlgorithmInformation-2009".to_string(),
3671        }]);
3672        let result = generate_c(&module).unwrap();
3673        assert!(
3674            result.contains("#include \"algorithm_information_2009.h\""),
3675            "import include missing:\n{}",
3676            result
3677        );
3678        assert!(
3679            result.contains("/* Imported module headers */"),
3680            "import section comment missing:\n{}",
3681            result
3682        );
3683    }
3684
3685    #[test]
3686    fn test_multiple_imports_generate_includes() {
3687        let module = make_import_module(vec![
3688            Import {
3689                symbols: vec!["Name".to_string()],
3690                module_name: "PKIX1Explicit88".to_string(),
3691            },
3692            Import {
3693                symbols: vec!["AlgorithmIdentifier".to_string()],
3694                module_name: "AlgorithmInformation-2009".to_string(),
3695            },
3696        ]);
3697        let result = generate_c(&module).unwrap();
3698        assert!(
3699            result.contains("#include \"pkix1_explicit88.h\""),
3700            "first import missing:\n{}",
3701            result
3702        );
3703        assert!(
3704            result.contains("#include \"algorithm_information_2009.h\""),
3705            "second import missing:\n{}",
3706            result
3707        );
3708    }
3709
3710    #[test]
3711    fn test_no_imports_no_import_section() {
3712        let module = make_import_module(vec![]);
3713        let result = generate_c(&module).unwrap();
3714        assert!(
3715            !result.contains("/* Imported module headers */"),
3716            "spurious import section:\n{}",
3717            result
3718        );
3719    }
3720
3721    // -----------------------------------------------------------------------
3722    // CHOICE with inline SEQUENCE/SET union member
3723    // -----------------------------------------------------------------------
3724
3725    #[test]
3726    fn test_choice_inline_sequence_generates_named_struct() {
3727        // A CHOICE variant with an anonymous inline SEQUENCE must be extracted
3728        // into a named struct `{ChoiceName}{VariantName}` and referenced by
3729        // value in the union — no void* placeholder.
3730        let module = Module {
3731            name: "TestModule".to_string(),
3732            oid: None,
3733            values: vec![],
3734            tagging_mode: None,
3735            imports: vec![],
3736            exports: vec![],
3737            definitions: vec![Definition {
3738                name: "MyChoice".to_string(),
3739                ty: Type::Choice(vec![
3740                    ChoiceVariant {
3741                        name: "seqVal".to_string(),
3742                        ty: Type::Sequence(vec![SequenceField {
3743                            name: "x".to_string(),
3744                            ty: Type::Integer(None, vec![]),
3745                            optional: false,
3746                            default: None,
3747                        }]),
3748                    },
3749                    ChoiceVariant {
3750                        name: "intVal".to_string(),
3751                        ty: Type::Integer(None, vec![]),
3752                    },
3753                ]),
3754            }],
3755        };
3756        let result = generate_c(&module).unwrap();
3757        // Anonymous SEQUENCE must become a named struct
3758        assert!(
3759            result.contains("struct MyChoiceSeqVal {"),
3760            "expected named struct MyChoiceSeqVal for inline SEQUENCE variant:\n{}",
3761            result
3762        );
3763        // Union member must reference the named struct by value (no void*)
3764        assert!(
3765            result.contains("MyChoiceSeqVal seq_val;"),
3766            "union member should be 'MyChoiceSeqVal seq_val;':\n{}",
3767            result
3768        );
3769        // No void* placeholder must remain
3770        assert!(
3771            !result.contains("void* seq_val"),
3772            "void* placeholder must not appear after expansion:\n{}",
3773            result
3774        );
3775        // Regular integer variant unchanged
3776        assert!(
3777            result.contains("SyntaInteger* int_val;"),
3778            "regular integer variant missing:\n{}",
3779            result
3780        );
3781        // MyChoiceSeqVal must be forward-declared before MyChoice
3782        let fwd_inner = result
3783            .find("typedef struct MyChoiceSeqVal")
3784            .unwrap_or(usize::MAX);
3785        let fwd_outer = result
3786            .find("typedef struct MyChoice MyChoice;")
3787            .unwrap_or(usize::MAX);
3788        assert!(
3789            fwd_inner < fwd_outer,
3790            "MyChoiceSeqVal forward decl must precede MyChoice:\n{}",
3791            result
3792        );
3793    }
3794
3795    #[test]
3796    fn test_sequence_all_optional_gets_default_prototype() {
3797        // A sequence where all fields are OPTIONAL (even with no explicit DEFAULT)
3798        // also qualifies for a _default() prototype since it can be zero-initialised.
3799        let module = make_default_module(vec![
3800            SequenceField {
3801                name: "host".to_string(),
3802                ty: Type::OctetString(None),
3803                optional: true,
3804                default: None,
3805            },
3806            SequenceField {
3807                name: "port".to_string(),
3808                ty: Type::Integer(None, vec![]),
3809                optional: true,
3810                default: None,
3811            },
3812        ]);
3813        let result = generate_c(&module).unwrap();
3814        assert!(
3815            result.contains("Config config_default(void);"),
3816            "prototype for all-optional sequence"
3817        );
3818    }
3819}