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