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 { tag: tag_info, .. } = ty {
1270        let cls = match tag_info.class {
1271            TagClass::Universal => format!("UNIVERSAL {}", tag_info.number),
1272            TagClass::Application => format!("APPLICATION {}", tag_info.number),
1273            TagClass::ContextSpecific => tag_info.number.to_string(),
1274            TagClass::Private => format!("PRIVATE {}", tag_info.number),
1275        };
1276        let mode = match tag_info.tagging {
1277            Tagging::Explicit => "EXPLICIT",
1278            Tagging::Implicit => "IMPLICIT",
1279        };
1280        Some(format!("[{}] {}", cls, mode))
1281    } else {
1282        None
1283    }
1284}
1285
1286/// Generate a struct definition for SEQUENCE type
1287fn generate_sequence_struct(
1288    output: &mut String,
1289    name: &str,
1290    fields: &[SequenceField],
1291) -> Result<(), Box<dyn std::error::Error>> {
1292    let struct_name = to_pascal_case(name);
1293    writeln!(output, "struct {} {{", struct_name)?;
1294
1295    for field in fields {
1296        // Skip NULL fields as they don't have data
1297        if matches!(field.ty, Type::Null) {
1298            continue;
1299        }
1300
1301        let field_name = to_snake_case(&field.name);
1302
1303        // Handle inline SEQUENCE/SET types
1304        match &field.ty {
1305            Type::Sequence(inner_fields) | Type::Set(inner_fields) => {
1306                // Generate inline anonymous struct
1307                if field.optional {
1308                    writeln!(output, "    bool has_{};", field_name)?;
1309                }
1310                writeln!(output, "    struct {{")?;
1311                for inner_field in inner_fields {
1312                    if matches!(inner_field.ty, Type::Null) {
1313                        continue;
1314                    }
1315                    let inner_name = to_snake_case(&inner_field.name);
1316                    let inner_type = get_c_type_for_field(&inner_field.ty);
1317                    if inner_field.optional {
1318                        writeln!(output, "        bool has_{};", inner_name)?;
1319                        writeln!(output, "        {} {};", inner_type, inner_name)?;
1320                    } else {
1321                        writeln!(output, "        {} {};", inner_type, inner_name)?;
1322                    }
1323                }
1324                writeln!(output, "    }} {};", field_name)?;
1325            }
1326            _ => {
1327                let field_type = get_c_type_for_field(&field.ty);
1328                let tag_note = tag_annotation_comment(&field.ty);
1329
1330                // For SEQUENCE OF / SET OF, generate length field
1331                if matches!(field.ty, Type::SequenceOf(_, _) | Type::SetOf(_, _)) {
1332                    writeln!(output, "    size_t {}_count;", field_name)?;
1333                    writeln!(output, "    {} {};", field_type, field_name)?;
1334                } else if field.optional {
1335                    writeln!(output, "    bool has_{};", field_name)?;
1336                    if let Some(ref tn) = tag_note {
1337                        writeln!(output, "    {} {}; /* {} */", field_type, field_name, tn)?;
1338                    } else {
1339                        writeln!(output, "    {} {};", field_type, field_name)?;
1340                    }
1341                } else if let Some(ref dv) = field.default {
1342                    if let Some(ref tn) = tag_note {
1343                        writeln!(
1344                            output,
1345                            "    {} {}; /* {} DEFAULT {} */",
1346                            field_type, field_name, tn, dv
1347                        )?;
1348                    } else {
1349                        writeln!(
1350                            output,
1351                            "    {} {}; /* DEFAULT {} */",
1352                            field_type, field_name, dv
1353                        )?;
1354                    }
1355                } else if let Some(ref tn) = tag_note {
1356                    writeln!(output, "    {} {}; /* {} */", field_type, field_name, tn)?;
1357                } else {
1358                    writeln!(output, "    {} {};", field_type, field_name)?;
1359                }
1360            }
1361        }
1362    }
1363
1364    writeln!(output, "}};")?;
1365    Ok(())
1366}
1367
1368/// Get C type for a field (handles embedded structs differently)
1369fn get_c_type_for_field(ty: &Type) -> String {
1370    match ty {
1371        Type::TypeRef(name) => to_pascal_case(name),
1372        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
1373            // Array types - use pointer with length
1374            format!("{}*", get_c_type(inner))
1375        }
1376        _ => get_c_type(ty),
1377    }
1378}
1379
1380/// Generate a struct definition for CHOICE type
1381fn generate_choice_struct(
1382    output: &mut String,
1383    name: &str,
1384    variants: &[ChoiceVariant],
1385) -> Result<(), Box<dyn std::error::Error>> {
1386    let struct_name = to_pascal_case(name);
1387    let tag_enum_name = format!("{}Tag", struct_name);
1388
1389    // Generate tag enum
1390    writeln!(output, "typedef enum {} {{", tag_enum_name)?;
1391    for (i, variant) in variants.iter().enumerate() {
1392        let variant_name = to_pascal_case(&variant.name);
1393        let comma = if i < variants.len() - 1 { "," } else { "" };
1394        writeln!(output, "    {}_{}{}", tag_enum_name, variant_name, comma)?;
1395    }
1396    writeln!(output, "}} {};", tag_enum_name)?;
1397    writeln!(output)?;
1398
1399    // Generate choice struct
1400    writeln!(output, "struct {} {{", struct_name)?;
1401    writeln!(output, "    {} tag;", tag_enum_name)?;
1402    writeln!(output, "    union {{")?;
1403
1404    for variant in variants {
1405        // Skip NULL types as they don't have data
1406        if matches!(variant.ty, Type::Null) {
1407            continue;
1408        }
1409
1410        let variant_name = to_snake_case(&variant.name);
1411        // Inline anonymous SEQUENCE/SET cannot be embedded by value in a C union;
1412        // emit a void* pointer with a comment so callers can cast to the right type.
1413        if matches!(peel_type(&variant.ty), Type::Sequence(_) | Type::Set(_)) {
1414            writeln!(
1415                output,
1416                "        void* {}; /* inline SEQUENCE/SET — define separately and cast */",
1417                variant_name
1418            )?;
1419        } else {
1420            let variant_type = get_c_type_for_field(&variant.ty);
1421            writeln!(output, "        {} {};", variant_type, variant_name)?;
1422        }
1423    }
1424
1425    writeln!(output, "    }} value;")?;
1426    writeln!(output, "}};")?;
1427    Ok(())
1428}
1429
1430/// Generate enum for INTEGER with named numbers
1431fn generate_enum_for_integer(
1432    output: &mut String,
1433    name: &str,
1434    named_numbers: &[NamedNumber],
1435) -> Result<(), Box<dyn std::error::Error>> {
1436    let enum_name = to_pascal_case(name);
1437    writeln!(output, "enum {} {{", enum_name)?;
1438
1439    for (i, nn) in named_numbers.iter().enumerate() {
1440        let variant_name = to_screaming_snake_case(&nn.name);
1441        let comma = if i < named_numbers.len() - 1 { "," } else { "" };
1442        writeln!(
1443            output,
1444            "    {}_{} = {}{}",
1445            enum_name, variant_name, nn.value, comma
1446        )?;
1447    }
1448
1449    writeln!(output, "}};")?;
1450    Ok(())
1451}
1452
1453/// Generate `#define` constants for an unconstrained INTEGER with named numbers.
1454///
1455/// # Why `int64_t`?
1456///
1457/// ASN.1 INTEGER is an unbounded type.  Named numbers (`{ tcp(6), udp(17) }`)
1458/// are symbolic labels for specific values; the actual DER-encoded value on
1459/// the wire may be any valid INTEGER, not just the listed names.  Using
1460/// `int64_t` matches what the runtime's `synta_integer_to_i64` returns, so
1461/// the decode/encode round-trip is correct over the full representable range
1462/// without truncation.
1463///
1464/// If a bounded representation is required, the schema should carry an
1465/// explicit size constraint (`INTEGER (0..65535)`), which causes the
1466/// constrained-integer path (`generate_constrained_integer_c`) to be taken
1467/// instead — that path wraps the value in a validated newtype struct.
1468///
1469/// # Output
1470///
1471/// The forward-declarations section already emits `typedef int64_t TypeName;`.
1472/// This function emits the accompanying preprocessor constants:
1473///
1474/// ```c
1475/// /* Named integer values for Protocol */
1476/// #define PROTOCOL_TCP  ((int64_t)6)
1477/// #define PROTOCOL_UDP  ((int64_t)17)
1478/// ```
1479///
1480/// Macro names are padded to the same length for visual alignment.
1481fn generate_defines_for_integer(
1482    output: &mut String,
1483    name: &str,
1484    named_numbers: &[NamedNumber],
1485) -> Result<(), Box<dyn std::error::Error>> {
1486    let type_name = to_pascal_case(name);
1487    let prefix = to_screaming_snake_case(name);
1488
1489    writeln!(output, "/* Named integer values for {} */", type_name)?;
1490
1491    // Pre-compute macro names to allow padding for alignment.
1492    let macro_names: Vec<String> = named_numbers
1493        .iter()
1494        .map(|nn| format!("{}_{}", prefix, to_snake_case(&nn.name).to_uppercase()))
1495        .collect();
1496    let max_len = macro_names.iter().map(|s| s.len()).max().unwrap_or(0);
1497
1498    for (nn, macro_name) in named_numbers.iter().zip(&macro_names) {
1499        writeln!(
1500            output,
1501            "#define {:width$} ((int64_t){})",
1502            macro_name,
1503            nn.value,
1504            width = max_len
1505        )?;
1506    }
1507
1508    Ok(())
1509}
1510
1511/// Generate encoder/decoder function prototypes
1512fn generate_encoder_decoder_prototypes(
1513    output: &mut String,
1514    def: &Definition,
1515    arena_mode: bool,
1516) -> Result<(), Box<dyn std::error::Error>> {
1517    let c_name = to_pascal_case(&def.name);
1518    let fn_prefix = to_snake_case(&def.name);
1519
1520    // Decoder prototype
1521    writeln!(
1522        output,
1523        "SyntaErrorCode {}_decode(SyntaDecoder* decoder, {}* out);",
1524        fn_prefix, c_name
1525    )?;
1526
1527    // Arena decoder prototype
1528    if arena_mode {
1529        writeln!(
1530            output,
1531            "SyntaErrorCode {}_decode_arena(SyntaDecoder* decoder, SyntaArena* arena, {}* out);",
1532            fn_prefix, c_name
1533        )?;
1534    }
1535
1536    // Encoder prototype
1537    writeln!(
1538        output,
1539        "SyntaErrorCode {}_encode(SyntaEncoder* encoder, const {}* value);",
1540        fn_prefix, c_name
1541    )?;
1542
1543    // Free function for complex types
1544    match &def.ty {
1545        Type::Sequence(_)
1546        | Type::Set(_)
1547        | Type::Choice(_)
1548        | Type::SequenceOf(_, _)
1549        | Type::SetOf(_, _) => {
1550            writeln!(output, "void {}_free({}* value);", fn_prefix, c_name)?;
1551        }
1552        _ => {}
1553    }
1554
1555    // Default constructor for sequences where every field is OPTIONAL or has a DEFAULT
1556    if let Type::Sequence(fields) | Type::Set(fields) = peel_type(&def.ty) {
1557        if fields.iter().all(|f| f.optional || f.default.is_some()) {
1558            writeln!(output, "{} {}_default(void);", c_name, fn_prefix)?;
1559        }
1560    }
1561
1562    Ok(())
1563}
1564
1565/// Generate helper functions (init, validate, print) for a definition.
1566fn generate_helper_functions(
1567    output: &mut String,
1568    def: &Definition,
1569) -> Result<(), Box<dyn std::error::Error>> {
1570    let c_name = to_pascal_case(&def.name);
1571    let fn_prefix = to_snake_case(&def.name);
1572
1573    match &def.ty {
1574        Type::Sequence(fields) | Type::Set(fields) => {
1575            generate_init_helper(output, &c_name, &fn_prefix)?;
1576            writeln!(output)?;
1577            generate_validate_sequence(output, &c_name, &fn_prefix, fields)?;
1578            writeln!(output)?;
1579            generate_print_sequence(output, &c_name, &fn_prefix, fields)?;
1580        }
1581        Type::Choice(variants) => {
1582            generate_validate_choice(output, &c_name, &fn_prefix, variants)?;
1583            writeln!(output)?;
1584            generate_print_choice(output, &c_name, &fn_prefix, variants)?;
1585        }
1586        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
1587            generate_init_helper(output, &c_name, &fn_prefix)?;
1588            writeln!(output)?;
1589            generate_validate_array(output, &c_name, &fn_prefix)?;
1590            writeln!(output)?;
1591            generate_print_array(output, &c_name, &fn_prefix, inner)?;
1592        }
1593        _ => {}
1594    }
1595
1596    Ok(())
1597}
1598
1599/// Emit a `static inline void <prefix>_init(<Name>* value)` zero-initialiser.
1600///
1601/// Uses `memset` to zero the entire struct so that optional `has_*` flags
1602/// start as `false` and all pointer fields start as `NULL`.
1603fn generate_init_helper(
1604    output: &mut String,
1605    c_name: &str,
1606    fn_prefix: &str,
1607) -> Result<(), Box<dyn std::error::Error>> {
1608    writeln!(
1609        output,
1610        "static inline void {}_init({}* value) {{",
1611        fn_prefix, c_name
1612    )?;
1613    writeln!(output, "    if (value != NULL) {{")?;
1614    writeln!(output, "        memset(value, 0, sizeof({}));", c_name)?;
1615    writeln!(output, "    }}")?;
1616    writeln!(output, "}}")?;
1617    Ok(())
1618}
1619
1620/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1621/// helper for a SEQUENCE or SET struct.
1622///
1623/// Required pointer fields are checked for non-`NULL`; optional pointer fields
1624/// are only checked when their `has_*` flag is `true`.  `NULL` fields return
1625/// `SyntaErrorCode_InvalidArgument`; value types (BOOLEAN, REAL, C enums, etc.)
1626/// are not checked.  Returns `SyntaErrorCode_Success` when all checks pass.
1627fn generate_validate_sequence(
1628    output: &mut String,
1629    c_name: &str,
1630    fn_prefix: &str,
1631    fields: &[SequenceField],
1632) -> Result<(), Box<dyn std::error::Error>> {
1633    writeln!(
1634        output,
1635        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1636        fn_prefix, c_name
1637    )?;
1638    writeln!(
1639        output,
1640        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1641    )?;
1642    for field in fields {
1643        if matches!(field.ty, Type::Null) {
1644            continue;
1645        }
1646        let field_name = to_snake_case(&field.name);
1647        if is_pointer_c_type(&field.ty) {
1648            if field.optional {
1649                writeln!(
1650                    output,
1651                    "    if (value->has_{name} && value->{name} == NULL) return SyntaErrorCode_InvalidArgument;",
1652                    name = field_name
1653                )?;
1654            } else {
1655                writeln!(
1656                    output,
1657                    "    if (value->{} == NULL) return SyntaErrorCode_InvalidArgument;",
1658                    field_name
1659                )?;
1660            }
1661        }
1662    }
1663    writeln!(output, "    return SyntaErrorCode_Success;")?;
1664    writeln!(output, "}}")?;
1665    Ok(())
1666}
1667
1668/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1669/// debugging helper for a SEQUENCE or SET struct.
1670///
1671/// Prints each field name and value via `fprintf`.  Optional absent fields are
1672/// printed as `(absent)`.  Requires `<stdio.h>`, which is included automatically
1673/// when `generate_helpers` is enabled in [`CCodeGenConfig`].
1674fn generate_print_sequence(
1675    output: &mut String,
1676    c_name: &str,
1677    fn_prefix: &str,
1678    fields: &[SequenceField],
1679) -> Result<(), Box<dyn std::error::Error>> {
1680    writeln!(
1681        output,
1682        "static inline void {}_print(const {}* value, FILE* stream) {{",
1683        fn_prefix, c_name
1684    )?;
1685    writeln!(
1686        output,
1687        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
1688        c_name
1689    )?;
1690    writeln!(output, "    fprintf(stream, \"{}{{\\n\");", c_name)?;
1691    for field in fields {
1692        if matches!(field.ty, Type::Null) {
1693            continue;
1694        }
1695        let field_name = to_snake_case(&field.name);
1696        write_sequence_field_print(output, &field_name, &field.ty, field.optional)?;
1697    }
1698    writeln!(output, "    fprintf(stream, \"}}\\n\");")?;
1699    writeln!(output, "}}")?;
1700    Ok(())
1701}
1702
1703/// Emit the fprintf statement(s) for one field inside a SEQUENCE print helper.
1704fn write_sequence_field_print(
1705    output: &mut String,
1706    field_name: &str,
1707    ty: &Type,
1708    optional: bool,
1709) -> Result<(), Box<dyn std::error::Error>> {
1710    let base = peel_type(ty);
1711
1712    // SequenceOf/SetOf store a count field separately; no has_ flag even when optional.
1713    if matches!(base, Type::SequenceOf(_, _) | Type::SetOf(_, _)) {
1714        write_field_value_print(
1715            output,
1716            field_name,
1717            base,
1718            &format!("value->{}", field_name),
1719            "    ",
1720        )?;
1721        return Ok(());
1722    }
1723
1724    if optional {
1725        writeln!(output, "    if (value->has_{}) {{", field_name)?;
1726        write_field_value_print(
1727            output,
1728            field_name,
1729            base,
1730            &format!("value->{}", field_name),
1731            "        ",
1732        )?;
1733        writeln!(output, "    }} else {{")?;
1734        writeln!(
1735            output,
1736            "        fprintf(stream, \"  {}: (absent)\\n\");",
1737            field_name
1738        )?;
1739        writeln!(output, "    }}")?;
1740    } else {
1741        write_field_value_print(
1742            output,
1743            field_name,
1744            base,
1745            &format!("value->{}", field_name),
1746            "    ",
1747        )?;
1748    }
1749    Ok(())
1750}
1751
1752/// Emit a fprintf statement for a single field value (type already peeled of Tagged/Constrained).
1753fn write_field_value_print(
1754    output: &mut String,
1755    field_name: &str,
1756    ty: &Type,
1757    field_expr: &str,
1758    indent: &str,
1759) -> Result<(), Box<dyn std::error::Error>> {
1760    match ty {
1761        Type::Boolean => {
1762            writeln!(
1763                output,
1764                "{}fprintf(stream, \"  {}: %s\\n\", {} ? \"true\" : \"false\");",
1765                indent, field_name, field_expr
1766            )?;
1767        }
1768        Type::Real => {
1769            writeln!(
1770                output,
1771                "{}fprintf(stream, \"  {}: %g\\n\", {});",
1772                indent, field_name, field_expr
1773            )?;
1774        }
1775        Type::OctetString(_)
1776        | Type::Utf8String(_)
1777        | Type::PrintableString(_)
1778        | Type::IA5String(_)
1779        | Type::UtcTime
1780        | Type::GeneralizedTime
1781        | Type::Any
1782        | Type::AnyDefinedBy(_) => {
1783            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1784            writeln!(
1785                output,
1786                "{}    fprintf(stream, \"  {}: <string(%zu bytes)>\\n\", synta_octet_string_len({}));",
1787                indent, field_name, field_expr
1788            )?;
1789            writeln!(output, "{}else", indent)?;
1790            writeln!(
1791                output,
1792                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1793                indent, field_name
1794            )?;
1795        }
1796        Type::Integer(_, _) | Type::Enumerated(_) => {
1797            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1798            writeln!(
1799                output,
1800                "{}    fprintf(stream, \"  {}: <integer>\\n\");",
1801                indent, field_name
1802            )?;
1803            writeln!(output, "{}else", indent)?;
1804            writeln!(
1805                output,
1806                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1807                indent, field_name
1808            )?;
1809        }
1810        Type::ObjectIdentifier => {
1811            writeln!(output, "{}if ({} != NULL)", indent, field_expr)?;
1812            writeln!(
1813                output,
1814                "{}    fprintf(stream, \"  {}: <oid>\\n\");",
1815                indent, field_name
1816            )?;
1817            writeln!(output, "{}else", indent)?;
1818            writeln!(
1819                output,
1820                "{}    fprintf(stream, \"  {}: NULL\\n\");",
1821                indent, field_name
1822            )?;
1823        }
1824        Type::BitString(_) => {
1825            writeln!(
1826                output,
1827                "{}fprintf(stream, \"  {}: <bit-string(%u bytes)>\\n\", (unsigned int){}.data.len);",
1828                indent, field_name, field_expr
1829            )?;
1830        }
1831        Type::TypeRef(name) => {
1832            let type_name = to_pascal_case(name);
1833            writeln!(
1834                output,
1835                "{}fprintf(stream, \"  {}: <{}>\\n\");",
1836                indent, field_name, type_name
1837            )?;
1838        }
1839        Type::Sequence(_) | Type::Set(_) => {
1840            writeln!(
1841                output,
1842                "{}fprintf(stream, \"  {}: <struct>\\n\");",
1843                indent, field_name
1844            )?;
1845        }
1846        Type::SequenceOf(_, _) | Type::SetOf(_, _) => {
1847            // field_expr is "value->field_name"; the count field is "value->field_name_count"
1848            writeln!(
1849                output,
1850                "{}fprintf(stream, \"  {}: [%zu elements]\\n\", {}_count);",
1851                indent, field_name, field_expr
1852            )?;
1853        }
1854        Type::Choice(_) => {
1855            writeln!(
1856                output,
1857                "{}fprintf(stream, \"  {}: <choice>\\n\");",
1858                indent, field_name
1859            )?;
1860        }
1861        Type::Null => {}
1862        // Tagged/Constrained are already peeled by the caller.
1863        _ => {
1864            writeln!(
1865                output,
1866                "{}fprintf(stream, \"  {}: <value>\\n\");",
1867                indent, field_name
1868            )?;
1869        }
1870    }
1871    Ok(())
1872}
1873
1874/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1875/// helper for a CHOICE type.
1876///
1877/// Validates the discriminant tag via a `switch` statement.  Any tag value
1878/// not listed in the CHOICE returns `SyntaErrorCode_InvalidEncoding`, preventing
1879/// access to the union through an unrecognised tag.
1880fn generate_validate_choice(
1881    output: &mut String,
1882    c_name: &str,
1883    fn_prefix: &str,
1884    variants: &[ChoiceVariant],
1885) -> Result<(), Box<dyn std::error::Error>> {
1886    let tag_enum = format!("{}Tag", c_name);
1887    writeln!(
1888        output,
1889        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1890        fn_prefix, c_name
1891    )?;
1892    writeln!(
1893        output,
1894        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1895    )?;
1896    writeln!(output, "    switch (value->tag) {{")?;
1897    for variant in variants {
1898        let variant_name = to_pascal_case(&variant.name);
1899        writeln!(output, "        case {}_{}: break;", tag_enum, variant_name)?;
1900    }
1901    writeln!(
1902        output,
1903        "        default: return SyntaErrorCode_InvalidEncoding;"
1904    )?;
1905    writeln!(output, "    }}")?;
1906    writeln!(output, "    return SyntaErrorCode_Success;")?;
1907    writeln!(output, "}}")?;
1908    Ok(())
1909}
1910
1911/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1912/// debugging helper for a CHOICE type.
1913///
1914/// Prints the active variant name via a `switch` on the discriminant tag.
1915/// Unknown tags are printed as `<unknown tag>`.
1916fn generate_print_choice(
1917    output: &mut String,
1918    c_name: &str,
1919    fn_prefix: &str,
1920    variants: &[ChoiceVariant],
1921) -> Result<(), Box<dyn std::error::Error>> {
1922    let tag_enum = format!("{}Tag", c_name);
1923    writeln!(
1924        output,
1925        "static inline void {}_print(const {}* value, FILE* stream) {{",
1926        fn_prefix, c_name
1927    )?;
1928    writeln!(
1929        output,
1930        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
1931        c_name
1932    )?;
1933    writeln!(output, "    switch (value->tag) {{")?;
1934    for variant in variants {
1935        let variant_name = to_pascal_case(&variant.name);
1936        let variant_field = to_snake_case(&variant.name);
1937        writeln!(output, "        case {}_{}:", tag_enum, variant_name)?;
1938        writeln!(
1939            output,
1940            "            fprintf(stream, \"{}{{ {} }}\\n\");",
1941            c_name, variant_field
1942        )?;
1943        writeln!(output, "            break;")?;
1944    }
1945    writeln!(output, "        default:")?;
1946    writeln!(
1947        output,
1948        "            fprintf(stream, \"{}{{ <unknown tag> }}\\n\");",
1949        c_name
1950    )?;
1951    writeln!(output, "            break;")?;
1952    writeln!(output, "    }}")?;
1953    writeln!(output, "}}")?;
1954    Ok(())
1955}
1956
1957/// Emit a `static inline SyntaErrorCode <prefix>_validate(const <Name>* value)`
1958/// helper for a SEQUENCE OF / SET OF type.
1959///
1960/// Checks that `items` is non-`NULL` whenever `count` is non-zero, guarding
1961/// against an inconsistent `{count: n, items: NULL}` state that would cause
1962/// undefined behaviour when the array is iterated.
1963fn generate_validate_array(
1964    output: &mut String,
1965    c_name: &str,
1966    fn_prefix: &str,
1967) -> Result<(), Box<dyn std::error::Error>> {
1968    writeln!(
1969        output,
1970        "static inline SyntaErrorCode {}_validate(const {}* value) {{",
1971        fn_prefix, c_name
1972    )?;
1973    writeln!(
1974        output,
1975        "    if (value == NULL) return SyntaErrorCode_NullPointer;"
1976    )?;
1977    writeln!(
1978        output,
1979        "    if (value->count > 0 && value->items == NULL) return SyntaErrorCode_InvalidArgument;"
1980    )?;
1981    writeln!(output, "    return SyntaErrorCode_Success;")?;
1982    writeln!(output, "}}")?;
1983    Ok(())
1984}
1985
1986/// Emit a `static inline void <prefix>_print(const <Name>* value, FILE* stream)`
1987/// debugging helper for a SEQUENCE OF / SET OF type.
1988///
1989/// Prints the element count.  Individual element values are not printed
1990/// because the element type may itself require a recursive print call; callers
1991/// that need per-element output should iterate and call the element's own
1992/// `_print` helper.
1993fn generate_print_array(
1994    output: &mut String,
1995    c_name: &str,
1996    fn_prefix: &str,
1997    _inner: &Type,
1998) -> Result<(), Box<dyn std::error::Error>> {
1999    writeln!(
2000        output,
2001        "static inline void {}_print(const {}* value, FILE* stream) {{",
2002        fn_prefix, c_name
2003    )?;
2004    writeln!(
2005        output,
2006        "    if (value == NULL) {{ fprintf(stream, \"{}(null)\\n\"); return; }}",
2007        c_name
2008    )?;
2009    writeln!(
2010        output,
2011        "    fprintf(stream, \"{}[%zu]\\n\", value->count);",
2012        c_name
2013    )?;
2014    writeln!(output, "}}")?;
2015    Ok(())
2016}
2017
2018/// Build a resolved OID registry from the module's value assignments.
2019///
2020/// OIDs that reference other named OIDs (e.g. `id-ori-kem ::= { id-ori 3 }`)
2021/// are resolved iteratively to a flat `Vec<u32>` of arc components.  Unresolvable
2022/// references (imported OID names not present in `values`) are left out of the
2023/// registry and generate a comment in the output instead.
2024fn build_c_oid_registry(values: &[ValueAssignment]) -> std::collections::HashMap<String, Vec<u32>> {
2025    use std::collections::HashMap;
2026    let mut registry: HashMap<String, Vec<u32>> = HashMap::new();
2027
2028    let mut changed = true;
2029    while changed {
2030        changed = false;
2031        for va in values {
2032            if registry.contains_key(&va.name) {
2033                continue;
2034            }
2035            if let Value::ObjectIdentifier(components) = &va.value {
2036                let mut resolved = Vec::new();
2037                let mut can_resolve = true;
2038                for component in components {
2039                    match component {
2040                        OidComponent::Number(n) => resolved.push(*n),
2041                        OidComponent::NamedRef(name) => {
2042                            if let Some(base) = registry.get(name) {
2043                                resolved.extend_from_slice(base);
2044                            } else {
2045                                can_resolve = false;
2046                                break;
2047                            }
2048                        }
2049                    }
2050                }
2051                if can_resolve {
2052                    registry.insert(va.name.clone(), resolved);
2053                    changed = true;
2054                }
2055            }
2056        }
2057    }
2058    registry
2059}
2060
2061/// Escape a string value for use as a C string literal (double-quoted).
2062///
2063/// Replaces backslashes (`\`) and double-quotes (`"`) with their C escape
2064/// sequences.  Other non-ASCII bytes are emitted as `\xNN` hex escapes.
2065fn escape_c_string(s: &str) -> String {
2066    let mut out = String::with_capacity(s.len());
2067    for ch in s.chars() {
2068        match ch {
2069            '\\' => out.push_str("\\\\"),
2070            '"' => out.push_str("\\\""),
2071            '\n' => out.push_str("\\n"),
2072            '\r' => out.push_str("\\r"),
2073            '\t' => out.push_str("\\t"),
2074            c if c.is_ascii() && (c as u8) >= 0x20 && (c as u8) < 0x7f => out.push(c),
2075            c => {
2076                for byte in c.to_string().as_bytes() {
2077                    out.push_str(&format!("\\x{:02x}", byte));
2078                }
2079            }
2080        }
2081    }
2082    out
2083}
2084
2085/// Emit C constant definitions for all value assignments in the module.
2086///
2087/// | ASN.1 value type  | C output                                                 |
2088/// |-------------------|----------------------------------------------------------|
2089/// | OBJECT IDENTIFIER | `static const uint32_t NAME[] = {...};`                  |
2090/// |                   | `#define NAME_LEN n`                                     |
2091/// | INTEGER           | `#define NAME ((int64_t)n)`                              |
2092/// | BOOLEAN           | `#define NAME (true)` / `#define NAME (false)`           |
2093/// | String            | `#define NAME "text"`                                    |
2094///
2095/// OID values that reference other named OIDs in the same module are resolved
2096/// to their full arc component sequence before being emitted.  References that
2097/// cannot be resolved (e.g. imported OID names) produce a comment instead.
2098fn generate_value_constants(
2099    output: &mut String,
2100    module: &Module,
2101) -> Result<(), Box<dyn std::error::Error>> {
2102    if module.values.is_empty() {
2103        return Ok(());
2104    }
2105
2106    writeln!(output, "/* Value constants */")?;
2107    writeln!(output)?;
2108
2109    let oid_registry = build_c_oid_registry(&module.values);
2110
2111    for va in &module.values {
2112        let c_name = to_screaming_snake_case(&va.name);
2113        match &va.value {
2114            Value::ObjectIdentifier(_) => {
2115                if let Some(arcs) = oid_registry.get(&va.name) {
2116                    write!(output, "static const uint32_t {}[] = {{", c_name)?;
2117                    for (i, n) in arcs.iter().enumerate() {
2118                        if i > 0 {
2119                            write!(output, ", ")?;
2120                        }
2121                        write!(output, "{}", n)?;
2122                    }
2123                    writeln!(output, "}};")?;
2124                    writeln!(output, "#define {}_LEN {}", c_name, arcs.len())?;
2125                    writeln!(output)?;
2126                } else {
2127                    writeln!(
2128                        output,
2129                        "/* OID {} could not be fully resolved (unresolved named reference) */",
2130                        va.name
2131                    )?;
2132                    writeln!(output)?;
2133                }
2134            }
2135            Value::Integer(n) => {
2136                writeln!(output, "#define {} ((int64_t){})", c_name, n)?;
2137                writeln!(output)?;
2138            }
2139            Value::Boolean(b) => {
2140                writeln!(
2141                    output,
2142                    "#define {} ({})",
2143                    c_name,
2144                    if *b { "true" } else { "false" }
2145                )?;
2146                writeln!(output)?;
2147            }
2148            Value::String(s) => {
2149                writeln!(output, "#define {} \"{}\"", c_name, escape_c_string(s))?;
2150                writeln!(output)?;
2151            }
2152        }
2153    }
2154
2155    Ok(())
2156}
2157
2158/// Strip `Tagged` and `Constrained` wrappers to reach the underlying base type.
2159fn peel_type(ty: &Type) -> &Type {
2160    match ty {
2161        Type::Tagged { inner, .. }
2162        | Type::Constrained {
2163            base_type: inner, ..
2164        } => peel_type(inner),
2165        _ => ty,
2166    }
2167}
2168
2169/// Return true if `ty` maps to a C pointer (requires a NULL-check in validate helpers).
2170fn is_pointer_c_type(ty: &Type) -> bool {
2171    matches!(
2172        peel_type(ty),
2173        Type::Integer(_, _)
2174            | Type::Enumerated(_)
2175            | Type::OctetString(_)
2176            | Type::ObjectIdentifier
2177            | Type::Utf8String(_)
2178            | Type::PrintableString(_)
2179            | Type::IA5String(_)
2180            | Type::UtcTime
2181            | Type::GeneralizedTime
2182            | Type::Any
2183            | Type::AnyDefinedBy(_)
2184    )
2185}
2186
2187/// Get the C type for an ASN.1 type
2188pub(crate) fn get_c_type(ty: &Type) -> String {
2189    match ty {
2190        Type::Integer(_, _) => "SyntaInteger*".to_string(),
2191        Type::Enumerated(_) => "SyntaInteger*".to_string(),
2192        Type::Real => "double".to_string(),
2193        Type::Boolean => "bool".to_string(),
2194        Type::OctetString(_) => "SyntaOctetString*".to_string(),
2195        Type::BitString(_) => "SyntaBitString".to_string(),
2196        Type::ObjectIdentifier => "SyntaObjectIdentifier*".to_string(),
2197        Type::Null => "void".to_string(),
2198        Type::Utf8String(_)
2199        | Type::PrintableString(_)
2200        | Type::IA5String(_)
2201        | Type::TeletexString(_)
2202        | Type::UniversalString(_)
2203        | Type::BmpString(_)
2204        | Type::GeneralString(_)
2205        | Type::NumericString(_)
2206        | Type::VisibleString(_) => {
2207            "SyntaOctetString*".to_string() // Use OctetString for strings (FFI may not have typed strings yet)
2208        }
2209        Type::UtcTime | Type::GeneralizedTime => "SyntaOctetString*".to_string(), // Use OctetString for now (FFI may not have Time yet)
2210        Type::Sequence(_) | Type::Set(_) => "struct /* complex type */".to_string(),
2211        Type::SequenceOf(inner, _) | Type::SetOf(inner, _) => {
2212            format!("{}*", get_c_type(inner))
2213        }
2214        Type::Choice(_) => "union /* choice */".to_string(),
2215        Type::TypeRef(name) => to_pascal_case(name),
2216        Type::Tagged { inner, .. } => get_c_type(inner),
2217        Type::Constrained { base_type, .. } => get_c_type(base_type),
2218        Type::Any => "SyntaOctetString*".to_string(), // ANY is encoded as OCTET STRING
2219        Type::AnyDefinedBy(_) => "SyntaOctetString*".to_string(), // ANY DEFINED BY also as OCTET STRING
2220        Type::Class(_) => "void /* class */".to_string(),         // IOC class — no C type
2221    }
2222}
2223
2224#[cfg(test)]
2225mod tests {
2226    use super::*;
2227
2228    #[test]
2229    fn test_generate_simple_sequence() {
2230        let module = Module {
2231            name: "TestModule".to_string(),
2232            oid: None,
2233            values: vec![],
2234            tagging_mode: None,
2235            imports: vec![],
2236            exports: vec![],
2237            definitions: vec![Definition {
2238                name: "SimpleSeq".to_string(),
2239                ty: Type::Sequence(vec![
2240                    SequenceField {
2241                        name: "version".to_string(),
2242                        ty: Type::Integer(None, vec![]),
2243                        optional: false,
2244                        default: None,
2245                    },
2246                    SequenceField {
2247                        name: "serialNumber".to_string(),
2248                        ty: Type::Integer(None, vec![]),
2249                        optional: false,
2250                        default: None,
2251                    },
2252                ]),
2253            }],
2254        };
2255
2256        let result = generate_c(&module).unwrap();
2257        assert!(result.contains("typedef struct SimpleSeq"));
2258        assert!(result.contains("SyntaInteger* version;"));
2259        assert!(result.contains("SyntaInteger* serial_number;"));
2260        assert!(result.contains("simple_seq_decode"));
2261        assert!(result.contains("simple_seq_encode"));
2262    }
2263
2264    #[test]
2265    fn test_generate_choice() {
2266        let module = Module {
2267            name: "TestModule".to_string(),
2268            oid: None,
2269            values: vec![],
2270            tagging_mode: None,
2271            imports: vec![],
2272            exports: vec![],
2273            definitions: vec![Definition {
2274                name: "MyChoice".to_string(),
2275                ty: Type::Choice(vec![
2276                    ChoiceVariant {
2277                        name: "intVal".to_string(),
2278                        ty: Type::Integer(None, vec![]),
2279                    },
2280                    ChoiceVariant {
2281                        name: "boolVal".to_string(),
2282                        ty: Type::Boolean,
2283                    },
2284                ]),
2285            }],
2286        };
2287
2288        let result = generate_c(&module).unwrap();
2289        assert!(result.contains("typedef enum MyChoiceTag"));
2290        assert!(result.contains("typedef struct MyChoice"));
2291        assert!(result.contains("MyChoiceTag tag;"));
2292        assert!(result.contains("union {"));
2293    }
2294
2295    #[test]
2296    fn test_generate_helpers() {
2297        let module = Module {
2298            name: "TestModule".to_string(),
2299            oid: None,
2300            values: vec![],
2301            tagging_mode: None,
2302            imports: vec![],
2303            exports: vec![],
2304            definitions: vec![
2305                Definition {
2306                    name: "SimpleSeq".to_string(),
2307                    ty: Type::Sequence(vec![
2308                        SequenceField {
2309                            name: "version".to_string(),
2310                            ty: Type::Integer(None, vec![]),
2311                            optional: false,
2312                            default: None,
2313                        },
2314                        SequenceField {
2315                            name: "flag".to_string(),
2316                            ty: Type::Boolean,
2317                            optional: true,
2318                            default: None,
2319                        },
2320                    ]),
2321                },
2322                Definition {
2323                    name: "MyChoice".to_string(),
2324                    ty: Type::Choice(vec![
2325                        ChoiceVariant {
2326                            name: "intVal".to_string(),
2327                            ty: Type::Integer(None, vec![]),
2328                        },
2329                        ChoiceVariant {
2330                            name: "boolVal".to_string(),
2331                            ty: Type::Boolean,
2332                        },
2333                    ]),
2334                },
2335            ],
2336        };
2337
2338        let config = CCodeGenConfig {
2339            generate_helpers: true,
2340            ..Default::default()
2341        };
2342        let result = generate_c_with_config(&module, config).unwrap();
2343
2344        // stdio.h included when helpers are on
2345        assert!(result.contains("#include <stdio.h>"));
2346
2347        // _init for SEQUENCE
2348        assert!(result.contains("simple_seq_init"));
2349        // _validate for SEQUENCE
2350        assert!(result.contains("simple_seq_validate"));
2351        assert!(result.contains("SyntaErrorCode_NullPointer"));
2352        // required integer field null-check
2353        assert!(result.contains("value->version == NULL"));
2354        // optional boolean field — no null check (not a pointer type)
2355        assert!(!result.contains("value->flag == NULL"));
2356        // _print for SEQUENCE
2357        assert!(result.contains("simple_seq_print"));
2358        assert!(result.contains("FILE* stream"));
2359
2360        // _validate for CHOICE
2361        assert!(result.contains("my_choice_validate"));
2362        assert!(result.contains("MyChoiceTag_IntVal"));
2363        assert!(result.contains("SyntaErrorCode_InvalidEncoding"));
2364        // _print for CHOICE
2365        assert!(result.contains("my_choice_print"));
2366    }
2367
2368    #[test]
2369    fn test_generate_named_integer() {
2370        let module = Module {
2371            name: "TestModule".to_string(),
2372            oid: None,
2373            values: vec![],
2374            tagging_mode: None,
2375            imports: vec![],
2376            exports: vec![],
2377            definitions: vec![Definition {
2378                name: "Protocol".to_string(),
2379                ty: Type::Integer(
2380                    None,
2381                    vec![
2382                        NamedNumber {
2383                            name: "tcp".to_string(),
2384                            value: 6,
2385                        },
2386                        NamedNumber {
2387                            name: "udp".to_string(),
2388                            value: 17,
2389                        },
2390                    ],
2391                ),
2392            }],
2393        };
2394
2395        let result = generate_c(&module).unwrap();
2396        // Forward declaration uses int64_t, not enum
2397        assert!(result.contains("typedef int64_t Protocol;"));
2398        // Named constants as #define macros
2399        assert!(result.contains("#define PROTOCOL_TCP"));
2400        assert!(result.contains("((int64_t)6)"));
2401        assert!(result.contains("#define PROTOCOL_UDP"));
2402        assert!(result.contains("((int64_t)17)"));
2403        // No C enum should be generated
2404        assert!(!result.contains("enum Protocol {"));
2405    }
2406
2407    // -----------------------------------------------------------------------
2408    // Constrained INTEGER tests
2409    // -----------------------------------------------------------------------
2410
2411    fn make_constrained_integer_module(
2412        type_name: &str,
2413        constraint: SubtypeConstraint,
2414        named_numbers: Vec<NamedNumber>,
2415    ) -> Module {
2416        Module {
2417            name: "TestModule".to_string(),
2418            oid: None,
2419            values: vec![],
2420            tagging_mode: None,
2421            imports: vec![],
2422            exports: vec![],
2423            definitions: vec![Definition {
2424                name: type_name.to_string(),
2425                ty: Type::Constrained {
2426                    base_type: Box::new(Type::Integer(None, named_numbers)),
2427                    constraint: Constraint {
2428                        spec: ConstraintSpec::Subtype(constraint),
2429                        exception: None,
2430                    },
2431                },
2432            }],
2433        }
2434    }
2435
2436    #[test]
2437    fn test_constrained_integer_value_range() {
2438        // Int32 ::= INTEGER (-2147483648..2147483647)
2439        let module = make_constrained_integer_module(
2440            "Int32",
2441            SubtypeConstraint::ValueRange {
2442                min: ConstraintValue::Integer(-2147483648),
2443                max: ConstraintValue::Integer(2147483647),
2444            },
2445            vec![],
2446        );
2447        let result = generate_c(&module).unwrap();
2448
2449        // Struct typedef — range fits int32_t so int32_t is chosen
2450        assert!(
2451            result.contains("typedef struct { int32_t value; } Int32;"),
2452            "missing struct typedef:\n{}",
2453            result
2454        );
2455        // Range displayed in comment
2456        assert!(result.contains("INTEGER (-2147483648..2147483647)"));
2457        // _new validated constructor
2458        assert!(result.contains("int32_new(int32_t v, Int32* out)"));
2459        assert!(result.contains("v >= -2147483648LL") && result.contains("v <= 2147483647LL"));
2460        // _new_unchecked
2461        assert!(result.contains("int32_new_unchecked(int32_t v)"));
2462        // _get accessor
2463        assert!(result.contains("int32_get(const Int32* self)"));
2464        // _validate
2465        assert!(result.contains("int32_validate(const Int32* self)"));
2466        // decode / encode prototypes
2467        assert!(result.contains("int32_decode(SyntaDecoder*"));
2468        assert!(result.contains("int32_encode(SyntaEncoder*"));
2469        // no _free (plain struct, no heap allocation)
2470        assert!(!result.contains("int32_free"));
2471    }
2472
2473    #[test]
2474    fn test_constrained_integer_single_value() {
2475        // PvNo ::= INTEGER (5)
2476        let module = make_constrained_integer_module(
2477            "PvNo",
2478            SubtypeConstraint::SingleValue(ConstraintValue::Integer(5)),
2479            vec![],
2480        );
2481        let result = generate_c(&module).unwrap();
2482
2483        // Single value 5 ≥ 0 → uint8_t
2484        assert!(result.contains("typedef struct { uint8_t value; } PvNo;"));
2485        assert!(result.contains("INTEGER (5)"));
2486        assert!(result.contains("v == 5LL"));
2487        assert!(result.contains("pv_no_new(uint8_t v, PvNo* out)"));
2488        assert!(result.contains("pv_no_validate(const PvNo* self)"));
2489    }
2490
2491    #[test]
2492    fn test_constrained_integer_min_max_unconstrained() {
2493        // UncheckedInt ::= INTEGER (MIN..MAX)  — all int64_t values valid
2494        let module = make_constrained_integer_module(
2495            "UncheckedInt",
2496            SubtypeConstraint::ValueRange {
2497                min: ConstraintValue::Min,
2498                max: ConstraintValue::Max,
2499            },
2500            vec![],
2501        );
2502        let result = generate_c(&module).unwrap();
2503
2504        assert!(result.contains("typedef struct { int64_t value; } UncheckedInt;"));
2505        // Check expression must be "1" (always true) — no bound to check
2506        assert!(result.contains("return 1;"));
2507        // The if(!()) guard must also evaluate to if(!(1)) which is always false, so
2508        // _new always succeeds.
2509        assert!(result.contains("if (!(1)) return false;"));
2510    }
2511
2512    #[test]
2513    fn test_constrained_integer_half_open_range() {
2514        // NonNegInt ::= INTEGER (0..MAX)
2515        let module = make_constrained_integer_module(
2516            "NonNegInt",
2517            SubtypeConstraint::ValueRange {
2518                min: ConstraintValue::Integer(0),
2519                max: ConstraintValue::Max,
2520            },
2521            vec![],
2522        );
2523        let result = generate_c(&module).unwrap();
2524
2525        // Only a lower bound should be generated (no upper bound for MAX)
2526        assert!(result.contains("v >= 0LL"));
2527        assert!(!result.contains("v <= INT64_MAX"));
2528    }
2529
2530    #[test]
2531    fn test_constrained_integer_union() {
2532        // SmallOrLarge ::= INTEGER (0..10 | 100..200)
2533        let module = make_constrained_integer_module(
2534            "SmallOrLarge",
2535            SubtypeConstraint::Union(vec![
2536                SubtypeConstraint::ValueRange {
2537                    min: ConstraintValue::Integer(0),
2538                    max: ConstraintValue::Integer(10),
2539                },
2540                SubtypeConstraint::ValueRange {
2541                    min: ConstraintValue::Integer(100),
2542                    max: ConstraintValue::Integer(200),
2543                },
2544            ]),
2545            vec![],
2546        );
2547        let result = generate_c(&module).unwrap();
2548
2549        assert!(result.contains("typedef struct { int64_t value; } SmallOrLarge;"));
2550        // Union operator
2551        assert!(result.contains("||"));
2552        assert!(result.contains("v >= 0LL") && result.contains("v <= 10LL"));
2553        assert!(result.contains("v >= 100LL") && result.contains("v <= 200LL"));
2554    }
2555
2556    #[test]
2557    fn test_constrained_integer_complement() {
2558        // NotZero ::= INTEGER (ALL EXCEPT 0)
2559        let module = make_constrained_integer_module(
2560            "NotZero",
2561            SubtypeConstraint::Complement(Box::new(SubtypeConstraint::SingleValue(
2562                ConstraintValue::Integer(0),
2563            ))),
2564            vec![],
2565        );
2566        let result = generate_c(&module).unwrap();
2567
2568        assert!(result.contains("typedef struct { int64_t value; } NotZero;"));
2569        assert!(result.contains("!(v == 0LL)"));
2570    }
2571
2572    #[test]
2573    fn test_constrained_integer_with_named_numbers() {
2574        // MsgType ::= INTEGER (0..30) — with named constants
2575        let module = make_constrained_integer_module(
2576            "MsgType",
2577            SubtypeConstraint::ValueRange {
2578                min: ConstraintValue::Integer(0),
2579                max: ConstraintValue::Integer(30),
2580            },
2581            vec![
2582                NamedNumber {
2583                    name: "asReq".to_string(),
2584                    value: 10,
2585                },
2586                NamedNumber {
2587                    name: "asRep".to_string(),
2588                    value: 11,
2589                },
2590            ],
2591        );
2592        let result = generate_c(&module).unwrap();
2593
2594        // Range 0..30 ≥ 0 → uint8_t
2595        assert!(result.contains("typedef struct { uint8_t value; } MsgType;"));
2596        // Named constants emitted as typed #define macros
2597        assert!(result.contains("#define MsgType_AS_REQ ((uint8_t)10)"));
2598        assert!(result.contains("#define MsgType_AS_REP ((uint8_t)11)"));
2599        // Still has validation helpers
2600        assert!(result.contains("msg_type_new(uint8_t v, MsgType* out)"));
2601        assert!(result.contains("msg_type_validate(const MsgType* self)"));
2602    }
2603
2604    #[test]
2605    fn test_format_c_constraint_display() {
2606        assert_eq!(
2607            format_c_constraint_display(&SubtypeConstraint::ValueRange {
2608                min: ConstraintValue::Integer(-128),
2609                max: ConstraintValue::Integer(127),
2610            }),
2611            "-128..127"
2612        );
2613        assert_eq!(
2614            format_c_constraint_display(&SubtypeConstraint::SingleValue(ConstraintValue::Integer(
2615                42
2616            ))),
2617            "42"
2618        );
2619        assert_eq!(
2620            format_c_constraint_display(&SubtypeConstraint::ValueRange {
2621                min: ConstraintValue::Min,
2622                max: ConstraintValue::Max,
2623            }),
2624            "MIN..MAX"
2625        );
2626    }
2627
2628    #[test]
2629    fn test_generate_c_constraint_check() {
2630        // Concrete range, signed type — both bounds emitted
2631        assert_eq!(
2632            generate_c_constraint_check(
2633                "val",
2634                &SubtypeConstraint::ValueRange {
2635                    min: ConstraintValue::Integer(0),
2636                    max: ConstraintValue::Integer(100),
2637                },
2638                "int64_t",
2639            ),
2640            "(val >= 0LL && val <= 100LL)"
2641        );
2642        // Same range, unsigned type — lower bound 0 is trivially satisfied; omitted
2643        assert_eq!(
2644            generate_c_constraint_check(
2645                "val",
2646                &SubtypeConstraint::ValueRange {
2647                    min: ConstraintValue::Integer(0),
2648                    max: ConstraintValue::Integer(100),
2649                },
2650                "uint8_t",
2651            ),
2652            "(val <= 100LL)"
2653        );
2654        // Single value — unsigned does not change equality check
2655        assert_eq!(
2656            generate_c_constraint_check(
2657                "val",
2658                &SubtypeConstraint::SingleValue(ConstraintValue::Integer(5)),
2659                "int64_t",
2660            ),
2661            "val == 5LL"
2662        );
2663        // MIN..MAX — always true regardless of type
2664        assert_eq!(
2665            generate_c_constraint_check(
2666                "val",
2667                &SubtypeConstraint::ValueRange {
2668                    min: ConstraintValue::Min,
2669                    max: ConstraintValue::Max,
2670                },
2671                "int64_t",
2672            ),
2673            "1"
2674        );
2675        // Half-open (lower-bounded only), signed — keep the check
2676        assert_eq!(
2677            generate_c_constraint_check(
2678                "val",
2679                &SubtypeConstraint::ValueRange {
2680                    min: ConstraintValue::Integer(0),
2681                    max: ConstraintValue::Max,
2682                },
2683                "int64_t",
2684            ),
2685            "(val >= 0LL)"
2686        );
2687        // Complement
2688        assert_eq!(
2689            generate_c_constraint_check(
2690                "val",
2691                &SubtypeConstraint::Complement(Box::new(SubtypeConstraint::SingleValue(
2692                    ConstraintValue::Integer(0)
2693                ))),
2694                "int64_t",
2695            ),
2696            "!(val == 0LL)"
2697        );
2698    }
2699
2700    // -----------------------------------------------------------------------
2701    // Constrained string tests
2702    // -----------------------------------------------------------------------
2703
2704    fn make_constrained_string_module(
2705        type_name: &str,
2706        base_ty: Type,
2707        constraint: SubtypeConstraint,
2708    ) -> Module {
2709        Module {
2710            name: "TestModule".to_string(),
2711            oid: None,
2712            values: vec![],
2713            tagging_mode: None,
2714            imports: vec![],
2715            exports: vec![],
2716            definitions: vec![Definition {
2717                name: type_name.to_string(),
2718                ty: Type::Constrained {
2719                    base_type: Box::new(base_ty),
2720                    constraint: Constraint {
2721                        spec: ConstraintSpec::Subtype(constraint),
2722                        exception: None,
2723                    },
2724                },
2725            }],
2726        }
2727    }
2728
2729    #[test]
2730    fn test_constrained_string_size_only() {
2731        // Realm ::= IA5String (SIZE (1..MAX))
2732        let module = make_constrained_string_module(
2733            "Realm",
2734            Type::IA5String(None),
2735            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2736                min: ConstraintValue::Integer(1),
2737                max: ConstraintValue::Max,
2738            })),
2739        );
2740        let result = generate_c(&module).unwrap();
2741
2742        // Struct typedef
2743        assert!(
2744            result.contains("typedef struct { SyntaByteArray value; } Realm;"),
2745            "missing struct typedef:\n{}",
2746            result
2747        );
2748        // Comment shows base type and constraint
2749        assert!(result.contains("/* IA5String (SIZE (1..MAX)) */"));
2750        // _new validated constructor
2751        assert!(result.contains("realm_new(SyntaByteArray value, Realm* out)"));
2752        assert!(result.contains("uint32_t _len = value.len;"));
2753        assert!(result.contains("_len >= 1U"));
2754        // _new_unchecked
2755        assert!(result.contains("realm_new_unchecked(SyntaByteArray value)"));
2756        // _get accessor
2757        assert!(result.contains("realm_get(const Realm* self)"));
2758        assert!(result.contains("r.owned = 0"));
2759        // _validate
2760        assert!(result.contains("realm_validate(const Realm* self)"));
2761        assert!(result.contains("uint32_t _len = self->value.len;"));
2762        // _free
2763        assert!(result.contains("realm_free(Realm* self)"));
2764        assert!(result.contains("synta_byte_array_free"));
2765        // decode / encode prototypes
2766        assert!(result.contains("realm_decode(SyntaDecoder*"));
2767        assert!(result.contains("realm_encode(SyntaEncoder*"));
2768    }
2769
2770    #[test]
2771    fn test_constrained_string_size_exact() {
2772        // FixedTag ::= OCTET STRING (SIZE (4))
2773        let module = make_constrained_string_module(
2774            "FixedTag",
2775            Type::OctetString(None),
2776            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::SingleValue(
2777                ConstraintValue::Integer(4),
2778            ))),
2779        );
2780        let result = generate_c(&module).unwrap();
2781
2782        assert!(result.contains("/* OCTET STRING (SIZE (4)) */"));
2783        assert!(result.contains("typedef struct { SyntaByteArray value; } FixedTag;"));
2784        assert!(result.contains("_len == 4U"));
2785        assert!(result.contains("fixed_tag_new(SyntaByteArray value, FixedTag* out)"));
2786        assert!(result.contains("fixed_tag_validate(const FixedTag* self)"));
2787        assert!(result.contains("fixed_tag_free(FixedTag* self)"));
2788    }
2789
2790    #[test]
2791    fn test_constrained_string_size_zero_min() {
2792        // OptStr ::= IA5String (SIZE (0..255))  — no lower bound check needed (uint32_t >= 0 always)
2793        let module = make_constrained_string_module(
2794            "OptStr",
2795            Type::IA5String(None),
2796            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2797                min: ConstraintValue::Integer(0),
2798                max: ConstraintValue::Integer(255),
2799            })),
2800        );
2801        let result = generate_c(&module).unwrap();
2802
2803        // Upper bound present
2804        assert!(result.contains("_len <= 255U"));
2805        // Lower bound 0 is always true for uint32_t — must NOT be emitted
2806        assert!(!result.contains("_len >= 0U"));
2807    }
2808
2809    #[test]
2810    fn test_constrained_string_min_max_size() {
2811        // AnyStr ::= IA5String (SIZE (MIN..MAX)) — always valid
2812        let module = make_constrained_string_module(
2813            "AnyStr",
2814            Type::IA5String(None),
2815            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2816                min: ConstraintValue::Min,
2817                max: ConstraintValue::Max,
2818            })),
2819        );
2820        let result = generate_c(&module).unwrap();
2821
2822        // Should produce "if (!(1)) return false;" — always passes
2823        assert!(result.contains("if (!(1)) return false;"));
2824    }
2825
2826    #[test]
2827    fn test_constrained_string_alphabet_only() {
2828        // DigitStr ::= IA5String (FROM ("0".."9"))
2829        let module = make_constrained_string_module(
2830            "DigitStr",
2831            Type::IA5String(None),
2832            SubtypeConstraint::PermittedAlphabet(vec![CharRange { min: '0', max: '9' }]),
2833        );
2834        let result = generate_c(&module).unwrap();
2835
2836        assert!(result.contains("/* IA5String (FROM (\"0\"..\"9\")) */"));
2837        // Alphabet loop structure
2838        assert!(result.contains("const unsigned char *_ap ="));
2839        assert!(result.contains("unsigned char _c = _ap[_i]"));
2840        assert!(result.contains("_ok = (_c >= '0' && _c <= '9')"));
2841        assert!(result.contains("if (!_ok) return false;"));
2842    }
2843
2844    #[test]
2845    fn test_constrained_string_alphabet_single_char() {
2846        // SingleChar ::= IA5String (FROM ("x"))
2847        let module = make_constrained_string_module(
2848            "SingleChar",
2849            Type::IA5String(None),
2850            SubtypeConstraint::PermittedAlphabet(vec![CharRange { min: 'x', max: 'x' }]),
2851        );
2852        let result = generate_c(&module).unwrap();
2853        // Single char uses == not range
2854        assert!(result.contains("_ok = _c == 'x'"));
2855    }
2856
2857    #[test]
2858    fn test_constrained_string_size_and_alphabet() {
2859        // VisStr ::= PrintableString (SIZE (1..64) FROM ("A".."Z" | "a".."z"))
2860        let module = make_constrained_string_module(
2861            "VisStr",
2862            Type::PrintableString(None),
2863            SubtypeConstraint::Intersection(vec![
2864                SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2865                    min: ConstraintValue::Integer(1),
2866                    max: ConstraintValue::Integer(64),
2867                })),
2868                SubtypeConstraint::PermittedAlphabet(vec![
2869                    CharRange { min: 'A', max: 'Z' },
2870                    CharRange { min: 'a', max: 'z' },
2871                ]),
2872            ]),
2873        );
2874        let result = generate_c(&module).unwrap();
2875
2876        assert!(result.contains("/* PrintableString"));
2877        // SIZE check first
2878        assert!(result.contains("_len >= 1U") && result.contains("_len <= 64U"));
2879        // Alphabet check
2880        assert!(result.contains("(_c >= 'A' && _c <= 'Z') || (_c >= 'a' && _c <= 'z')"));
2881        // _validate also has both checks
2882        assert!(result.contains("uint32_t _len = self->value.len;"));
2883    }
2884
2885    #[test]
2886    fn test_constrained_string_pattern_placeholder() {
2887        // PatStr ::= IA5String (PATTERN "[0-9]+")
2888        let module = make_constrained_string_module(
2889            "PatStr",
2890            Type::IA5String(None),
2891            SubtypeConstraint::Pattern("[0-9]+".to_string()),
2892        );
2893        let result = generate_c(&module).unwrap();
2894
2895        assert!(result.contains("PATTERN constraint \"[0-9]+\" not enforced at runtime"));
2896        // Still emits the struct and helpers
2897        assert!(result.contains("typedef struct { SyntaByteArray value; } PatStr;"));
2898        assert!(result.contains("pat_str_new(SyntaByteArray value, PatStr* out)"));
2899    }
2900
2901    #[test]
2902    fn test_constrained_utf8string() {
2903        // Label ::= UTF8String (SIZE (1..255))
2904        let module = make_constrained_string_module(
2905            "Label",
2906            Type::Utf8String(None),
2907            SubtypeConstraint::SizeConstraint(Box::new(SubtypeConstraint::ValueRange {
2908                min: ConstraintValue::Integer(1),
2909                max: ConstraintValue::Integer(255),
2910            })),
2911        );
2912        let result = generate_c(&module).unwrap();
2913
2914        assert!(result.contains("/* UTF8String (SIZE (1..255)) */"));
2915        assert!(result.contains("typedef struct { SyntaByteArray value; } Label;"));
2916        assert!(result.contains("label_new(SyntaByteArray value, Label* out)"));
2917        assert!(result.contains("label_free(Label* self)"));
2918    }
2919
2920    #[test]
2921    fn test_generate_c_length_check() {
2922        // Exact length
2923        assert_eq!(
2924            generate_c_length_check(
2925                "_len",
2926                &SubtypeConstraint::SingleValue(ConstraintValue::Integer(4))
2927            ),
2928            "_len == 4U"
2929        );
2930        // Range 1..MAX — only lower bound
2931        assert_eq!(
2932            generate_c_length_check(
2933                "_len",
2934                &SubtypeConstraint::ValueRange {
2935                    min: ConstraintValue::Integer(1),
2936                    max: ConstraintValue::Max,
2937                }
2938            ),
2939            "(_len >= 1U)"
2940        );
2941        // Range 0..256 — lower bound 0 omitted
2942        assert_eq!(
2943            generate_c_length_check(
2944                "_len",
2945                &SubtypeConstraint::ValueRange {
2946                    min: ConstraintValue::Integer(0),
2947                    max: ConstraintValue::Integer(256),
2948                }
2949            ),
2950            "(_len <= 256U)"
2951        );
2952        // MIN..MAX — always true
2953        assert_eq!(
2954            generate_c_length_check(
2955                "_len",
2956                &SubtypeConstraint::ValueRange {
2957                    min: ConstraintValue::Min,
2958                    max: ConstraintValue::Max,
2959                }
2960            ),
2961            "1"
2962        );
2963    }
2964
2965    #[test]
2966    fn test_format_c_char_literal() {
2967        assert_eq!(format_c_char_literal('A'), "'A'");
2968        assert_eq!(format_c_char_literal('0'), "'0'");
2969        assert_eq!(format_c_char_literal('\''), "'\\''");
2970        assert_eq!(format_c_char_literal('\\'), "'\\\\'");
2971        assert_eq!(format_c_char_literal('\x01'), "'\\x01'");
2972    }
2973
2974    // -----------------------------------------------------------------------
2975    // Named-bit BIT STRING constants — Task 7
2976    // -----------------------------------------------------------------------
2977
2978    fn make_named_bit_module(name: &str, bits: Vec<NamedNumber>) -> Module {
2979        Module {
2980            name: "TestModule".to_string(),
2981            oid: None,
2982            values: vec![],
2983            tagging_mode: None,
2984            imports: vec![],
2985            exports: vec![],
2986            definitions: vec![Definition {
2987                name: name.to_string(),
2988                ty: Type::Constrained {
2989                    base_type: Box::new(Type::BitString(None)),
2990                    constraint: Constraint {
2991                        spec: ConstraintSpec::Subtype(SubtypeConstraint::NamedBitList(bits)),
2992                        exception: None,
2993                    },
2994                },
2995            }],
2996        }
2997    }
2998
2999    #[test]
3000    fn test_named_bit_string_typedef_and_defines() {
3001        // TicketFlags ::= BIT STRING { reserved(0), forwardable(1), proxiable(3) }
3002        let module = make_named_bit_module(
3003            "TicketFlags",
3004            vec![
3005                NamedNumber {
3006                    name: "reserved".to_string(),
3007                    value: 0,
3008                },
3009                NamedNumber {
3010                    name: "forwardable".to_string(),
3011                    value: 1,
3012                },
3013                NamedNumber {
3014                    name: "proxiable".to_string(),
3015                    value: 3,
3016                },
3017            ],
3018        );
3019        let result = generate_c(&module).unwrap();
3020
3021        // Typedef
3022        assert!(
3023            result.contains("typedef SyntaBitString TicketFlags;"),
3024            "typedef present"
3025        );
3026        // Named bit defines: prefix is TICKET_FLAGS (snake_case of TicketFlags)
3027        assert!(
3028            result.contains("TICKET_FLAGS_RESERVED_BIT"),
3029            "reserved bit define"
3030        );
3031        assert!(
3032            result.contains("TICKET_FLAGS_FORWARDABLE_BIT"),
3033            "forwardable bit define"
3034        );
3035        assert!(
3036            result.contains("TICKET_FLAGS_PROXIABLE_BIT"),
3037            "proxiable bit define"
3038        );
3039        // Values
3040        assert!(
3041            result.contains("TICKET_FLAGS_RESERVED_BIT") && result.contains(" 0"),
3042            "value 0 present"
3043        );
3044        assert!(result.contains(" 1"), "value 1 present");
3045        assert!(result.contains(" 3"), "value 3 present");
3046        // No helper macros without --with-helpers
3047        assert!(!result.contains("IS_SET"), "no IS_SET without helpers");
3048    }
3049
3050    #[test]
3051    fn test_named_bit_string_hyphenated_name() {
3052        // kdc-options ::= BIT STRING { reserved(0), forwardable(1) }
3053        let module = make_named_bit_module(
3054            "kdc-options",
3055            vec![
3056                NamedNumber {
3057                    name: "reserved".to_string(),
3058                    value: 0,
3059                },
3060                NamedNumber {
3061                    name: "forwardable".to_string(),
3062                    value: 1,
3063                },
3064            ],
3065        );
3066        let result = generate_c(&module).unwrap();
3067        // Hyphenated name → PascalCase typedef, SCREAMING prefix with underscore
3068        assert!(
3069            result.contains("typedef SyntaBitString KdcOptions;"),
3070            "typedef with PascalCase"
3071        );
3072        assert!(
3073            result.contains("KDC_OPTIONS_RESERVED_BIT"),
3074            "hyphenated prefix uses underscore"
3075        );
3076        assert!(
3077            result.contains("KDC_OPTIONS_FORWARDABLE_BIT"),
3078            "forwardable bit define"
3079        );
3080    }
3081
3082    #[test]
3083    fn test_named_bit_string_camel_case_bit_name() {
3084        // digitalSignature(0) bit name should become DIGITAL_SIGNATURE
3085        let module = make_named_bit_module(
3086            "KeyUsage",
3087            vec![
3088                NamedNumber {
3089                    name: "digitalSignature".to_string(),
3090                    value: 0,
3091                },
3092                NamedNumber {
3093                    name: "nonRepudiation".to_string(),
3094                    value: 1,
3095                },
3096            ],
3097        );
3098        let result = generate_c(&module).unwrap();
3099        // KeyUsage → KEY_USAGE prefix; camelCase bit names → SCREAMING_SNAKE
3100        assert!(
3101            result.contains("KEY_USAGE_DIGITAL_SIGNATURE_BIT"),
3102            "camelCase bit → SCREAMING_SNAKE"
3103        );
3104        assert!(
3105            result.contains("KEY_USAGE_NON_REPUDIATION_BIT"),
3106            "nonRepudiation bit"
3107        );
3108    }
3109
3110    #[test]
3111    fn test_named_bit_string_with_helpers() {
3112        let module = make_named_bit_module(
3113            "TicketFlags",
3114            vec![NamedNumber {
3115                name: "forwardable".to_string(),
3116                value: 1,
3117            }],
3118        );
3119        let config = CCodeGenConfig {
3120            generate_helpers: true,
3121            ..Default::default()
3122        };
3123        let result = generate_c_with_config(&module, config).unwrap();
3124        // Helper macros present (prefix TICKET_FLAGS from snake_case of TicketFlags)
3125        assert!(
3126            result.contains("TICKET_FLAGS_IS_SET(bs, bit)"),
3127            "IS_SET helper"
3128        );
3129        assert!(result.contains("TICKET_FLAGS_SET(bs, bit)"), "SET helper");
3130        assert!(
3131            result.contains("TICKET_FLAGS_CLEAR(bs, bit)"),
3132            "CLEAR helper"
3133        );
3134        // Helper macros reference the C API functions
3135        assert!(result.contains("synta_bitstring_is_set"), "is_set API call");
3136        assert!(result.contains("synta_bitstring_set"), "set API call");
3137        assert!(result.contains("synta_bitstring_clear"), "clear API call");
3138    }
3139
3140    #[test]
3141    fn test_named_bit_string_empty_list() {
3142        // BIT STRING with empty NamedBitList — just a typedef, no defines
3143        let module = make_named_bit_module("EmptyFlags", vec![]);
3144        let result = generate_c(&module).unwrap();
3145        assert!(
3146            result.contains("typedef SyntaBitString EmptyFlags;"),
3147            "typedef present"
3148        );
3149        assert!(
3150            !result.contains("EMPTY_FLAGS_"),
3151            "no defines for empty list"
3152        );
3153    }
3154
3155    // -----------------------------------------------------------------------
3156    // Named-bit BIT STRING + SIZE constraint — Task 12
3157    //
3158    // BIT STRING { bits } (SIZE N..MAX) is parsed as
3159    //   Intersection([NamedBitList(bits), SizeConstraint(...)]).
3160    // The C codegen must extract the NamedBitList and generate typedef + #defines,
3161    // not fall through to generate_constrained_string_c.
3162    // -----------------------------------------------------------------------
3163
3164    fn make_named_bit_with_size_module(name: &str, bits: Vec<NamedNumber>) -> Module {
3165        // Produces BIT STRING { bits } (SIZE (32..MAX))
3166        Module {
3167            name: "TestModule".to_string(),
3168            oid: None,
3169            values: vec![],
3170            tagging_mode: None,
3171            imports: vec![],
3172            exports: vec![],
3173            definitions: vec![Definition {
3174                name: name.to_string(),
3175                ty: Type::Constrained {
3176                    base_type: Box::new(Type::BitString(None)),
3177                    constraint: Constraint {
3178                        spec: ConstraintSpec::Subtype(SubtypeConstraint::Intersection(vec![
3179                            SubtypeConstraint::NamedBitList(bits),
3180                            SubtypeConstraint::SizeConstraint(Box::new(
3181                                SubtypeConstraint::ValueRange {
3182                                    min: ConstraintValue::Integer(32),
3183                                    max: ConstraintValue::Max,
3184                                },
3185                            )),
3186                        ])),
3187                        exception: None,
3188                    },
3189                },
3190            }],
3191        }
3192    }
3193
3194    #[test]
3195    fn test_named_bit_string_with_size_emits_typedef() {
3196        // BIT STRING { bits } (SIZE (32..MAX)) → typedef SyntaBitString, not struct
3197        let module = make_named_bit_with_size_module(
3198            "TicketFlags",
3199            vec![
3200                NamedNumber {
3201                    name: "forwardable".to_string(),
3202                    value: 1,
3203                },
3204                NamedNumber {
3205                    name: "proxiable".to_string(),
3206                    value: 3,
3207                },
3208            ],
3209        );
3210        let result = generate_c(&module).unwrap();
3211        assert!(
3212            result.contains("typedef SyntaBitString TicketFlags;"),
3213            "combined form must still typedef SyntaBitString; got:\n{}",
3214            result
3215        );
3216        assert!(
3217            result.contains("#define TICKET_FLAGS_FORWARDABLE_BIT"),
3218            "FORWARDABLE_BIT define must appear; got:\n{}",
3219            result
3220        );
3221        assert!(
3222            result.contains("#define TICKET_FLAGS_PROXIABLE_BIT"),
3223            "PROXIABLE_BIT define must appear; got:\n{}",
3224            result
3225        );
3226        // Must NOT produce a struct { SyntaByteArray value; }
3227        assert!(
3228            !result.contains("typedef struct { SyntaByteArray value; } TicketFlags;"),
3229            "combined form must not fall through to constrained-string struct; got:\n{}",
3230            result
3231        );
3232    }
3233
3234    #[test]
3235    fn test_named_bit_string_with_size_helpers() {
3236        // Helpers should still be emitted for the combined form when enabled
3237        let module = make_named_bit_with_size_module(
3238            "KdcOptions",
3239            vec![NamedNumber {
3240                name: "forwardable".to_string(),
3241                value: 1,
3242            }],
3243        );
3244        let config = CCodeGenConfig {
3245            generate_helpers: true,
3246            ..Default::default()
3247        };
3248        let result = generate_c_with_config(&module, config).unwrap();
3249        assert!(
3250            result.contains("KDC_OPTIONS_IS_SET(bs, bit)"),
3251            "IS_SET helper must appear; got:\n{}",
3252            result
3253        );
3254    }
3255
3256    // -----------------------------------------------------------------------
3257    // DEFAULT value annotation tests
3258    // -----------------------------------------------------------------------
3259
3260    fn make_default_module(fields: Vec<SequenceField>) -> Module {
3261        Module {
3262            name: "TestModule".to_string(),
3263            oid: None,
3264            values: vec![],
3265            tagging_mode: None,
3266            imports: vec![],
3267            exports: vec![],
3268            definitions: vec![Definition {
3269                name: "Config".to_string(),
3270                ty: Type::Sequence(fields),
3271            }],
3272        }
3273    }
3274
3275    #[test]
3276    fn test_sequence_default_comment_in_struct() {
3277        // Fields with a DEFAULT value get an inline /* DEFAULT … */ comment.
3278        let module = make_default_module(vec![
3279            SequenceField {
3280                name: "port".to_string(),
3281                ty: Type::Integer(None, vec![]),
3282                optional: false,
3283                default: Some("8080".to_string()),
3284            },
3285            SequenceField {
3286                name: "enabled".to_string(),
3287                ty: Type::Boolean,
3288                optional: false,
3289                default: Some("TRUE".to_string()),
3290            },
3291        ]);
3292        let result = generate_c(&module).unwrap();
3293        assert!(
3294            result.contains("SyntaInteger* port; /* DEFAULT 8080 */"),
3295            "integer default comment"
3296        );
3297        assert!(
3298            result.contains("bool enabled; /* DEFAULT TRUE */"),
3299            "boolean default comment"
3300        );
3301    }
3302
3303    #[test]
3304    fn test_sequence_default_prototype_generated() {
3305        // When every field is OPTIONAL or has a DEFAULT, a _default() prototype appears.
3306        let module = make_default_module(vec![SequenceField {
3307            name: "port".to_string(),
3308            ty: Type::Integer(None, vec![]),
3309            optional: false,
3310            default: Some("8080".to_string()),
3311        }]);
3312        let result = generate_c(&module).unwrap();
3313        assert!(
3314            result.contains("Config config_default(void);"),
3315            "default prototype generated"
3316        );
3317    }
3318
3319    #[test]
3320    fn test_sequence_no_default_prototype_for_required_field() {
3321        // A required field with no DEFAULT means no _default() prototype.
3322        let module = make_default_module(vec![SequenceField {
3323            name: "name".to_string(),
3324            ty: Type::Integer(None, vec![]),
3325            optional: false,
3326            default: None,
3327        }]);
3328        let result = generate_c(&module).unwrap();
3329        assert!(
3330            !result.contains("config_default(void)"),
3331            "no prototype for required-only sequence"
3332        );
3333    }
3334
3335    // -----------------------------------------------------------------------
3336    // Tag annotation comment tests
3337    // -----------------------------------------------------------------------
3338
3339    fn make_tagged_seq_module(
3340        field_name: &str,
3341        class: TagClass,
3342        number: u32,
3343        tagging: Tagging,
3344        inner: Type,
3345    ) -> Module {
3346        Module {
3347            name: "TestModule".to_string(),
3348            oid: None,
3349            values: vec![],
3350            tagging_mode: None,
3351            imports: vec![],
3352            exports: vec![],
3353            definitions: vec![Definition {
3354                name: "Msg".to_string(),
3355                ty: Type::Sequence(vec![SequenceField {
3356                    name: field_name.to_string(),
3357                    ty: Type::Tagged {
3358                        tag: TagInfo {
3359                            class,
3360                            number,
3361                            tagging,
3362                        },
3363                        inner: Box::new(inner),
3364                    },
3365                    optional: false,
3366                    default: None,
3367                }]),
3368            }],
3369        }
3370    }
3371
3372    #[test]
3373    fn test_explicit_tag_annotation_in_struct() {
3374        // [0] EXPLICIT INTEGER → comment on struct field
3375        let module = make_tagged_seq_module(
3376            "id",
3377            TagClass::ContextSpecific,
3378            0,
3379            Tagging::Explicit,
3380            Type::Integer(None, vec![]),
3381        );
3382        let result = generate_c(&module).unwrap();
3383        assert!(
3384            result.contains("SyntaInteger* id; /* [0] EXPLICIT */"),
3385            "explicit tag comment missing; got:\n{}",
3386            result
3387        );
3388    }
3389
3390    #[test]
3391    fn test_implicit_tag_annotation_in_struct() {
3392        // [1] IMPLICIT OCTET STRING → comment on struct field
3393        let module = make_tagged_seq_module(
3394            "data",
3395            TagClass::ContextSpecific,
3396            1,
3397            Tagging::Implicit,
3398            Type::OctetString(None),
3399        );
3400        let result = generate_c(&module).unwrap();
3401        assert!(
3402            result.contains("SyntaOctetString* data; /* [1] IMPLICIT */"),
3403            "implicit tag comment missing; got:\n{}",
3404            result
3405        );
3406    }
3407
3408    #[test]
3409    fn test_application_tag_annotation_in_struct() {
3410        // [APPLICATION 2] IMPLICIT INTEGER → comment on struct field
3411        let module = make_tagged_seq_module(
3412            "val",
3413            TagClass::Application,
3414            2,
3415            Tagging::Implicit,
3416            Type::Integer(None, vec![]),
3417        );
3418        let result = generate_c(&module).unwrap();
3419        assert!(
3420            result.contains("SyntaInteger* val; /* [APPLICATION 2] IMPLICIT */"),
3421            "APPLICATION tag comment missing; got:\n{}",
3422            result
3423        );
3424    }
3425
3426    // -----------------------------------------------------------------------
3427    // Value constants (OID arrays, integer/boolean/string defines)
3428    // -----------------------------------------------------------------------
3429
3430    fn make_values_module(values: Vec<crate::ast::ValueAssignment>) -> Module {
3431        Module {
3432            name: "TestModule".to_string(),
3433            oid: None,
3434            values,
3435            tagging_mode: None,
3436            imports: vec![],
3437            exports: vec![],
3438            definitions: vec![],
3439        }
3440    }
3441
3442    #[test]
3443    fn test_oid_value_constant_emitted() {
3444        // id-ori OBJECT IDENTIFIER ::= { 1 2 840 113549 1 9 16 13 }
3445        let module = make_values_module(vec![crate::ast::ValueAssignment {
3446            name: "id-ori".to_string(),
3447            ty: Type::ObjectIdentifier,
3448            value: Value::ObjectIdentifier(vec![
3449                OidComponent::Number(1),
3450                OidComponent::Number(2),
3451                OidComponent::Number(840),
3452                OidComponent::Number(113549),
3453                OidComponent::Number(1),
3454                OidComponent::Number(9),
3455                OidComponent::Number(16),
3456                OidComponent::Number(13),
3457            ]),
3458        }]);
3459        let result = generate_c(&module).unwrap();
3460        assert!(
3461            result.contains("static const uint32_t ID_ORI[] = {1, 2, 840, 113549, 1, 9, 16, 13};"),
3462            "OID array missing:\n{}",
3463            result
3464        );
3465        assert!(
3466            result.contains("#define ID_ORI_LEN 8"),
3467            "_LEN define missing:\n{}",
3468            result
3469        );
3470    }
3471
3472    #[test]
3473    fn test_oid_named_reference_resolved() {
3474        // id-ori       ::= { 1 2 840 113549 1 9 16 13 }
3475        // id-ori-kem   ::= { id-ori 3 }
3476        let module = make_values_module(vec![
3477            crate::ast::ValueAssignment {
3478                name: "id-ori".to_string(),
3479                ty: Type::ObjectIdentifier,
3480                value: Value::ObjectIdentifier(vec![
3481                    OidComponent::Number(1),
3482                    OidComponent::Number(2),
3483                    OidComponent::Number(840),
3484                    OidComponent::Number(113549),
3485                    OidComponent::Number(1),
3486                    OidComponent::Number(9),
3487                    OidComponent::Number(16),
3488                    OidComponent::Number(13),
3489                ]),
3490            },
3491            crate::ast::ValueAssignment {
3492                name: "id-ori-kem".to_string(),
3493                ty: Type::ObjectIdentifier,
3494                value: Value::ObjectIdentifier(vec![
3495                    OidComponent::NamedRef("id-ori".to_string()),
3496                    OidComponent::Number(3),
3497                ]),
3498            },
3499        ]);
3500        let result = generate_c(&module).unwrap();
3501        assert!(
3502            result.contains(
3503                "static const uint32_t ID_ORI_KEM[] = {1, 2, 840, 113549, 1, 9, 16, 13, 3};"
3504            ),
3505            "resolved child OID missing:\n{}",
3506            result
3507        );
3508        assert!(
3509            result.contains("#define ID_ORI_KEM_LEN 9"),
3510            "_LEN for child OID missing:\n{}",
3511            result
3512        );
3513    }
3514
3515    #[test]
3516    fn test_oid_unresolvable_named_ref_emits_comment() {
3517        // An OID that references an undefined name should emit a comment, not a crash.
3518        let module = make_values_module(vec![crate::ast::ValueAssignment {
3519            name: "my-oid".to_string(),
3520            ty: Type::ObjectIdentifier,
3521            value: Value::ObjectIdentifier(vec![
3522                OidComponent::NamedRef("undefined-base".to_string()),
3523                OidComponent::Number(1),
3524            ]),
3525        }]);
3526        let result = generate_c(&module).unwrap();
3527        assert!(
3528            result.contains("could not be fully resolved"),
3529            "unresolvable OID should produce a comment:\n{}",
3530            result
3531        );
3532        // Must not crash or produce a broken array
3533        assert!(
3534            !result.contains("static const uint32_t MY_OID[] ="),
3535            "broken array must not be emitted:\n{}",
3536            result
3537        );
3538    }
3539
3540    #[test]
3541    fn test_integer_value_constant() {
3542        let module = make_values_module(vec![crate::ast::ValueAssignment {
3543            name: "max-count".to_string(),
3544            ty: Type::Integer(None, vec![]),
3545            value: Value::Integer(256),
3546        }]);
3547        let result = generate_c(&module).unwrap();
3548        assert!(
3549            result.contains("#define MAX_COUNT ((int64_t)256)"),
3550            "integer constant missing:\n{}",
3551            result
3552        );
3553    }
3554
3555    #[test]
3556    fn test_boolean_value_constant() {
3557        let module = make_values_module(vec![
3558            crate::ast::ValueAssignment {
3559                name: "flag-true".to_string(),
3560                ty: Type::Boolean,
3561                value: Value::Boolean(true),
3562            },
3563            crate::ast::ValueAssignment {
3564                name: "flag-false".to_string(),
3565                ty: Type::Boolean,
3566                value: Value::Boolean(false),
3567            },
3568        ]);
3569        let result = generate_c(&module).unwrap();
3570        assert!(
3571            result.contains("#define FLAG_TRUE (true)"),
3572            "true constant missing:\n{}",
3573            result
3574        );
3575        assert!(
3576            result.contains("#define FLAG_FALSE (false)"),
3577            "false constant missing:\n{}",
3578            result
3579        );
3580    }
3581
3582    #[test]
3583    fn test_string_value_constant() {
3584        let module = make_values_module(vec![crate::ast::ValueAssignment {
3585            name: "default-realm".to_string(),
3586            ty: Type::Utf8String(None),
3587            value: Value::String("EXAMPLE.COM".to_string()),
3588        }]);
3589        let result = generate_c(&module).unwrap();
3590        assert!(
3591            result.contains("#define DEFAULT_REALM \"EXAMPLE.COM\""),
3592            "string constant missing:\n{}",
3593            result
3594        );
3595    }
3596
3597    #[test]
3598    fn test_string_escape_in_constant() {
3599        let module = make_values_module(vec![crate::ast::ValueAssignment {
3600            name: "path".to_string(),
3601            ty: Type::Utf8String(None),
3602            value: Value::String("C:\\foo\\bar".to_string()),
3603        }]);
3604        let result = generate_c(&module).unwrap();
3605        assert!(
3606            result.contains("#define PATH \"C:\\\\foo\\\\bar\""),
3607            "backslash not escaped:\n{}",
3608            result
3609        );
3610    }
3611
3612    #[test]
3613    fn test_value_constants_section_header() {
3614        // When there are value assignments, the "Value constants" comment must appear.
3615        let module = make_values_module(vec![crate::ast::ValueAssignment {
3616            name: "x".to_string(),
3617            ty: Type::Integer(None, vec![]),
3618            value: Value::Integer(1),
3619        }]);
3620        let result = generate_c(&module).unwrap();
3621        assert!(
3622            result.contains("/* Value constants */"),
3623            "section comment missing"
3624        );
3625    }
3626
3627    #[test]
3628    fn test_no_value_constants_section_when_empty() {
3629        // When there are no value assignments, no "Value constants" comment.
3630        let module = make_values_module(vec![]);
3631        let result = generate_c(&module).unwrap();
3632        assert!(
3633            !result.contains("/* Value constants */"),
3634            "spurious section comment"
3635        );
3636    }
3637
3638    // -----------------------------------------------------------------------
3639    // Import #include generation
3640    // -----------------------------------------------------------------------
3641
3642    fn make_import_module(imports: Vec<Import>) -> Module {
3643        Module {
3644            name: "TestModule".to_string(),
3645            oid: None,
3646            values: vec![],
3647            tagging_mode: None,
3648            imports,
3649            exports: vec![],
3650            definitions: vec![],
3651        }
3652    }
3653
3654    #[test]
3655    fn test_import_generates_include() {
3656        // IMPORTS AlgorithmIdentifier FROM AlgorithmInformation-2009
3657        let module = make_import_module(vec![Import {
3658            symbols: vec!["AlgorithmIdentifier".to_string()],
3659            module_name: "AlgorithmInformation-2009".to_string(),
3660        }]);
3661        let result = generate_c(&module).unwrap();
3662        assert!(
3663            result.contains("#include \"algorithm_information_2009.h\""),
3664            "import include missing:\n{}",
3665            result
3666        );
3667        assert!(
3668            result.contains("/* Imported module headers */"),
3669            "import section comment missing:\n{}",
3670            result
3671        );
3672    }
3673
3674    #[test]
3675    fn test_multiple_imports_generate_includes() {
3676        let module = make_import_module(vec![
3677            Import {
3678                symbols: vec!["Name".to_string()],
3679                module_name: "PKIX1Explicit88".to_string(),
3680            },
3681            Import {
3682                symbols: vec!["AlgorithmIdentifier".to_string()],
3683                module_name: "AlgorithmInformation-2009".to_string(),
3684            },
3685        ]);
3686        let result = generate_c(&module).unwrap();
3687        assert!(
3688            result.contains("#include \"pkix1_explicit88.h\""),
3689            "first import missing:\n{}",
3690            result
3691        );
3692        assert!(
3693            result.contains("#include \"algorithm_information_2009.h\""),
3694            "second import missing:\n{}",
3695            result
3696        );
3697    }
3698
3699    #[test]
3700    fn test_no_imports_no_import_section() {
3701        let module = make_import_module(vec![]);
3702        let result = generate_c(&module).unwrap();
3703        assert!(
3704            !result.contains("/* Imported module headers */"),
3705            "spurious import section:\n{}",
3706            result
3707        );
3708    }
3709
3710    // -----------------------------------------------------------------------
3711    // CHOICE with inline SEQUENCE/SET union member
3712    // -----------------------------------------------------------------------
3713
3714    #[test]
3715    fn test_choice_inline_sequence_generates_named_struct() {
3716        // A CHOICE variant with an anonymous inline SEQUENCE must be extracted
3717        // into a named struct `{ChoiceName}{VariantName}` and referenced by
3718        // value in the union — no void* placeholder.
3719        let module = Module {
3720            name: "TestModule".to_string(),
3721            oid: None,
3722            values: vec![],
3723            tagging_mode: None,
3724            imports: vec![],
3725            exports: vec![],
3726            definitions: vec![Definition {
3727                name: "MyChoice".to_string(),
3728                ty: Type::Choice(vec![
3729                    ChoiceVariant {
3730                        name: "seqVal".to_string(),
3731                        ty: Type::Sequence(vec![SequenceField {
3732                            name: "x".to_string(),
3733                            ty: Type::Integer(None, vec![]),
3734                            optional: false,
3735                            default: None,
3736                        }]),
3737                    },
3738                    ChoiceVariant {
3739                        name: "intVal".to_string(),
3740                        ty: Type::Integer(None, vec![]),
3741                    },
3742                ]),
3743            }],
3744        };
3745        let result = generate_c(&module).unwrap();
3746        // Anonymous SEQUENCE must become a named struct
3747        assert!(
3748            result.contains("struct MyChoiceSeqVal {"),
3749            "expected named struct MyChoiceSeqVal for inline SEQUENCE variant:\n{}",
3750            result
3751        );
3752        // Union member must reference the named struct by value (no void*)
3753        assert!(
3754            result.contains("MyChoiceSeqVal seq_val;"),
3755            "union member should be 'MyChoiceSeqVal seq_val;':\n{}",
3756            result
3757        );
3758        // No void* placeholder must remain
3759        assert!(
3760            !result.contains("void* seq_val"),
3761            "void* placeholder must not appear after expansion:\n{}",
3762            result
3763        );
3764        // Regular integer variant unchanged
3765        assert!(
3766            result.contains("SyntaInteger* int_val;"),
3767            "regular integer variant missing:\n{}",
3768            result
3769        );
3770        // MyChoiceSeqVal must be forward-declared before MyChoice
3771        let fwd_inner = result
3772            .find("typedef struct MyChoiceSeqVal")
3773            .unwrap_or(usize::MAX);
3774        let fwd_outer = result
3775            .find("typedef struct MyChoice MyChoice;")
3776            .unwrap_or(usize::MAX);
3777        assert!(
3778            fwd_inner < fwd_outer,
3779            "MyChoiceSeqVal forward decl must precede MyChoice:\n{}",
3780            result
3781        );
3782    }
3783
3784    #[test]
3785    fn test_sequence_all_optional_gets_default_prototype() {
3786        // A sequence where all fields are OPTIONAL (even with no explicit DEFAULT)
3787        // also qualifies for a _default() prototype since it can be zero-initialised.
3788        let module = make_default_module(vec![
3789            SequenceField {
3790                name: "host".to_string(),
3791                ty: Type::OctetString(None),
3792                optional: true,
3793                default: None,
3794            },
3795            SequenceField {
3796                name: "port".to_string(),
3797                ty: Type::Integer(None, vec![]),
3798                optional: true,
3799                default: None,
3800            },
3801        ]);
3802        let result = generate_c(&module).unwrap();
3803        assert!(
3804            result.contains("Config config_default(void);"),
3805            "prototype for all-optional sequence"
3806        );
3807    }
3808}