Skip to main content

logicaffeine_compile/codegen/
ffi.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt::Write;
3
4use crate::analysis::registry::{FieldDef, FieldType, TypeDef, TypeRegistry, VariantDef};
5use crate::ast::stmt::{Expr, Stmt, TypeExpr};
6use crate::intern::{Interner, Symbol};
7
8use super::detection::is_result_type;
9use super::marshal::is_text_type;
10use super::types::codegen_type_expr;
11
12/// FFI: Detect if any function is exported for WASM.
13/// Used to emit `use wasm_bindgen::prelude::*;` preamble.
14pub(crate) fn has_wasm_exports(stmts: &[Stmt], interner: &Interner) -> bool {
15    stmts.iter().any(|stmt| {
16        if let Stmt::FunctionDef { is_exported: true, export_target: Some(target), .. } = stmt {
17            interner.resolve(*target).eq_ignore_ascii_case("wasm")
18        } else {
19            false
20        }
21    })
22}
23
24/// FFI: Detect if any function is exported for C ABI.
25/// Used to emit the LogosStatus runtime preamble and CStr/CString imports.
26pub(crate) fn has_c_exports(stmts: &[Stmt], interner: &Interner) -> bool {
27    stmts.iter().any(|stmt| {
28        if let Stmt::FunctionDef { is_exported: true, export_target, .. } = stmt {
29            match export_target {
30                None => true,
31                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
32            }
33        } else {
34            false
35        }
36    })
37}
38
39/// FFI: Detect if any C-exported function uses Text (String) types.
40/// Used to emit `use std::ffi::{CStr, CString};` preamble.
41pub(crate) fn has_c_exports_with_text(stmts: &[Stmt], interner: &Interner) -> bool {
42    stmts.iter().any(|stmt| {
43        if let Stmt::FunctionDef { is_exported: true, export_target, params, return_type, .. } = stmt {
44            let is_c = match export_target {
45                None => true,
46                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
47            };
48            if !is_c { return false; }
49            let has_text_param = params.iter().any(|(_, ty)| is_text_type(ty, interner));
50            let has_text_return = return_type.as_ref().map_or(false, |ty| is_text_type(ty, interner));
51            has_text_param || has_text_return
52        } else {
53            false
54        }
55    })
56}
57
58// =============================================================================
59// Universal ABI: Status-Code Error Runtime
60// =============================================================================
61
62/// Classification of a LOGOS type for C ABI boundary crossing.
63///
64/// Value types are passed directly as `#[repr(C)]` values.
65/// Reference types are passed as opaque handles with accessor functions.
66#[derive(Debug, Clone, Copy, PartialEq)]
67pub enum CAbiClass {
68    /// Passed directly by value (primitives, Text, small flat structs).
69    ValueType,
70    /// Passed as opaque `logos_handle_t` with generated accessors and free function.
71    ReferenceType,
72}
73
74/// Classify a LOGOS TypeExpr for C ABI boundary crossing.
75///
76/// Value types: Int, Nat, Real, Bool, Byte, Char, Text, small user structs (all value-type fields).
77/// Reference types: Seq, Map, Set, Option of reference, Result, large/recursive user types.
78pub(crate) fn classify_type_for_c_abi(ty: &TypeExpr, interner: &Interner, registry: &TypeRegistry) -> CAbiClass {
79    match ty {
80        TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
81            let name = interner.resolve(*sym);
82            match name {
83                "Int" | "Nat" | "Real" | "Float" | "Bool" | "Boolean"
84                | "Byte" | "Char" | "Unit" => CAbiClass::ValueType,
85                "Text" | "String" => CAbiClass::ValueType,
86                _ => {
87                    // Check registry for user-defined types
88                    if let Some(type_def) = registry.get(*sym) {
89                        match type_def {
90                            TypeDef::Struct { fields, .. } => {
91                                // Small struct with all value-type fields → ValueType
92                                let all_value = fields.iter().all(|f| {
93                                    is_value_type_field(&f.ty, interner)
94                                });
95                                if all_value && fields.len() <= 4 {
96                                    CAbiClass::ValueType
97                                } else {
98                                    CAbiClass::ReferenceType
99                                }
100                            }
101                            TypeDef::Enum { .. } => CAbiClass::ReferenceType,
102                            TypeDef::Primitive => CAbiClass::ValueType,
103                            TypeDef::Generic { .. } => CAbiClass::ReferenceType,
104                            TypeDef::Alias { .. } => CAbiClass::ValueType,
105                        }
106                    } else {
107                        CAbiClass::ValueType // Unknown → pass through
108                    }
109                }
110            }
111        }
112        TypeExpr::Refinement { base, .. } => classify_type_for_c_abi(base, interner, registry),
113        TypeExpr::Generic { base, .. } => {
114            let base_name = interner.resolve(*base);
115            match base_name {
116                "Option" | "Maybe" => {
117                    // Option of value type → value type (struct { present, value })
118                    // Option of reference type → reference type
119                    // For simplicity, treat all Options as reference types for now
120                    CAbiClass::ReferenceType
121                }
122                "Result" | "Seq" | "List" | "Vec" | "Map" | "HashMap"
123                | "Set" | "HashSet" => CAbiClass::ReferenceType,
124                _ => CAbiClass::ReferenceType,
125            }
126        }
127        TypeExpr::Function { .. } => CAbiClass::ReferenceType,
128        TypeExpr::Persistent { .. } => CAbiClass::ReferenceType,
129        TypeExpr::Mutable { inner } => classify_type_for_c_abi(inner, interner, registry),
130    }
131}
132
133/// Check if a field type is a C ABI value type (for struct classification).
134fn is_value_type_field(ft: &FieldType, interner: &Interner) -> bool {
135    match ft {
136        FieldType::Primitive(sym) | FieldType::Named(sym) => {
137            let name = interner.resolve(*sym);
138            matches!(name, "Int" | "Nat" | "Real" | "Float" | "Bool" | "Boolean"
139                | "Byte" | "Char" | "Unit")
140            // Text/String excluded — String cannot cross C ABI by value
141        }
142        FieldType::Generic { .. } => false, // Generic fields are reference types
143        FieldType::TypeParam(_) => false,
144    }
145}
146
147pub(crate) fn mangle_type_for_c(ty: &TypeExpr, interner: &Interner) -> String {
148    match ty {
149        TypeExpr::Mutable { inner } => mangle_type_for_c(inner, interner),
150        TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
151            let name = interner.resolve(*sym);
152            match name {
153                "Int" => "i64".to_string(),
154                "Nat" => "u64".to_string(),
155                "Real" | "Float" => "f64".to_string(),
156                "Bool" | "Boolean" => "bool".to_string(),
157                "Byte" => "u8".to_string(),
158                "Char" => "char".to_string(),
159                "Text" | "String" => "string".to_string(),
160                other => other.to_lowercase(),
161            }
162        }
163        TypeExpr::Refinement { base, .. } => mangle_type_for_c(base, interner),
164        TypeExpr::Generic { base, params } => {
165            let base_name = interner.resolve(*base);
166            let param_strs: Vec<String> = params.iter()
167                .map(|p| mangle_type_for_c(p, interner))
168                .collect();
169            match base_name {
170                "Seq" | "List" | "Vec" => format!("seq_{}", param_strs.join("_")),
171                "Map" | "HashMap" => format!("map_{}", param_strs.join("_")),
172                "Set" | "HashSet" => format!("set_{}", param_strs.join("_")),
173                "Option" | "Maybe" => format!("option_{}", param_strs.join("_")),
174                "Result" => format!("result_{}", param_strs.join("_")),
175                other => format!("{}_{}", other.to_lowercase(), param_strs.join("_")),
176            }
177        }
178        TypeExpr::Function { .. } => "fn".to_string(),
179        TypeExpr::Persistent { inner } => mangle_type_for_c(inner, interner),
180    }
181}
182
183/// Generate the LogosStatus runtime preamble for C ABI exports.
184///
185/// Emits:
186/// - `LogosStatus` repr(C) enum
187/// - Thread-local error storage
188/// - `logos_get_last_error()` and `logos_clear_error()` extern C functions
189/// - `logos_free_string()` for freeing allocated CStrings
190pub(crate) fn codegen_logos_runtime_preamble() -> String {
191    let mut out = String::new();
192
193    writeln!(out, "// ═══ LogicAffeine Universal ABI Runtime ═══\n").unwrap();
194
195    // LogosStatus enum
196    writeln!(out, "#[repr(C)]").unwrap();
197    writeln!(out, "#[derive(Debug, Clone, Copy, PartialEq)]").unwrap();
198    writeln!(out, "pub enum LogosStatus {{").unwrap();
199    writeln!(out, "    Ok = 0,").unwrap();
200    writeln!(out, "    Error = 1,").unwrap();
201    writeln!(out, "    RefinementViolation = 2,").unwrap();
202    writeln!(out, "    NullPointer = 3,").unwrap();
203    writeln!(out, "    OutOfBounds = 4,").unwrap();
204    writeln!(out, "    DeserializationFailed = 5,").unwrap();
205    writeln!(out, "    InvalidHandle = 6,").unwrap();
206    writeln!(out, "    ContainsNullByte = 7,").unwrap();
207    writeln!(out, "    ThreadPanic = 8,").unwrap();
208    writeln!(out, "    MemoryExhausted = 9,").unwrap();
209    writeln!(out, "}}\n").unwrap();
210
211    // Opaque handle type alias
212    writeln!(out, "pub type LogosHandle = *mut std::ffi::c_void;\n").unwrap();
213
214    // Thread-safe error storage (keyed by ThreadId)
215    writeln!(out, "fn logos_error_store() -> &'static std::sync::Mutex<std::collections::HashMap<std::thread::ThreadId, String>> {{").unwrap();
216    writeln!(out, "    use std::sync::OnceLock;").unwrap();
217    writeln!(out, "    static STORE: OnceLock<std::sync::Mutex<std::collections::HashMap<std::thread::ThreadId, String>>> = OnceLock::new();").unwrap();
218    writeln!(out, "    STORE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()))").unwrap();
219    writeln!(out, "}}\n").unwrap();
220
221    // set_last_error helper (not exported, internal only)
222    writeln!(out, "fn logos_set_last_error(msg: String) {{").unwrap();
223    writeln!(out, "    let mut store = logos_error_store().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
224    writeln!(out, "    store.insert(std::thread::current().id(), msg);").unwrap();
225    writeln!(out, "}}\n").unwrap();
226
227    // logos_last_error (exported) — canonical name
228    // Uses a thread-local CString cache to avoid dangling pointers.
229    // The returned pointer is valid until the next call to logos_last_error on the same thread.
230    writeln!(out, "thread_local! {{").unwrap();
231    writeln!(out, "    static LOGOS_ERROR_CACHE: std::cell::RefCell<Option<std::ffi::CString>> = std::cell::RefCell::new(None);").unwrap();
232    writeln!(out, "}}\n").unwrap();
233
234    writeln!(out, "#[no_mangle]").unwrap();
235    writeln!(out, "pub extern \"C\" fn logos_last_error() -> *const std::os::raw::c_char {{").unwrap();
236    writeln!(out, "    let msg = logos_error_store().lock().unwrap_or_else(|e| e.into_inner())").unwrap();
237    writeln!(out, "        .get(&std::thread::current().id()).cloned();").unwrap();
238    writeln!(out, "    match msg {{").unwrap();
239    writeln!(out, "        Some(s) => match std::ffi::CString::new(s) {{").unwrap();
240    writeln!(out, "            Ok(cstr) => {{").unwrap();
241    writeln!(out, "                let ptr = cstr.as_ptr();").unwrap();
242    writeln!(out, "                LOGOS_ERROR_CACHE.with(|cache| {{ cache.borrow_mut().replace(cstr); }});").unwrap();
243    writeln!(out, "                LOGOS_ERROR_CACHE.with(|cache| {{").unwrap();
244    writeln!(out, "                    cache.borrow().as_ref().map_or(std::ptr::null(), |c| c.as_ptr())").unwrap();
245    writeln!(out, "                }})").unwrap();
246    writeln!(out, "            }}").unwrap();
247    writeln!(out, "            Err(_) => std::ptr::null(),").unwrap();
248    writeln!(out, "        }}").unwrap();
249    writeln!(out, "        None => std::ptr::null(),").unwrap();
250    writeln!(out, "    }}").unwrap();
251    writeln!(out, "}}\n").unwrap();
252
253    // logos_get_last_error (exported) — backwards-compatible alias
254    writeln!(out, "#[no_mangle]").unwrap();
255    writeln!(out, "pub extern \"C\" fn logos_get_last_error() -> *const std::os::raw::c_char {{").unwrap();
256    writeln!(out, "    logos_last_error()").unwrap();
257    writeln!(out, "}}\n").unwrap();
258
259    // logos_clear_error (exported)
260    writeln!(out, "#[no_mangle]").unwrap();
261    writeln!(out, "pub extern \"C\" fn logos_clear_error() {{").unwrap();
262    writeln!(out, "    let mut store = logos_error_store().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
263    writeln!(out, "    store.remove(&std::thread::current().id());").unwrap();
264    writeln!(out, "}}\n").unwrap();
265
266    // logos_free_string (exported) — for freeing CStrings returned by accessors/JSON helpers
267    writeln!(out, "#[no_mangle]").unwrap();
268    writeln!(out, "pub extern \"C\" fn logos_free_string(ptr: *mut std::os::raw::c_char) {{").unwrap();
269    writeln!(out, "    if !ptr.is_null() {{").unwrap();
270    writeln!(out, "        unsafe {{ drop(std::ffi::CString::from_raw(ptr)); }}").unwrap();
271    writeln!(out, "    }}").unwrap();
272    writeln!(out, "}}\n").unwrap();
273
274    // ABI version constant and introspection functions
275    writeln!(out, "pub const LOGOS_ABI_VERSION: u32 = 1;\n").unwrap();
276
277    writeln!(out, "#[no_mangle]").unwrap();
278    writeln!(out, "pub extern \"C\" fn logos_version() -> *const std::os::raw::c_char {{").unwrap();
279    writeln!(out, "    concat!(env!(\"CARGO_PKG_VERSION\"), \"\\0\").as_ptr() as *const std::os::raw::c_char").unwrap();
280    writeln!(out, "}}\n").unwrap();
281
282    writeln!(out, "#[no_mangle]").unwrap();
283    writeln!(out, "pub extern \"C\" fn logos_abi_version() -> u32 {{").unwrap();
284    writeln!(out, "    LOGOS_ABI_VERSION").unwrap();
285    writeln!(out, "}}\n").unwrap();
286
287    // Handle registry with generation counters for use-after-free protection
288    writeln!(out, "struct HandleEntry {{").unwrap();
289    writeln!(out, "    data: usize,").unwrap();
290    writeln!(out, "    generation: u64,").unwrap();
291    writeln!(out, "}}\n").unwrap();
292
293    writeln!(out, "struct HandleRegistry {{").unwrap();
294    writeln!(out, "    entries: std::collections::HashMap<u64, HandleEntry>,").unwrap();
295    writeln!(out, "    counter: u64,").unwrap();
296    writeln!(out, "}}\n").unwrap();
297
298    writeln!(out, "impl HandleRegistry {{").unwrap();
299    writeln!(out, "    fn new() -> Self {{").unwrap();
300    writeln!(out, "        HandleRegistry {{ entries: std::collections::HashMap::new(), counter: 0 }}").unwrap();
301    writeln!(out, "    }}").unwrap();
302    writeln!(out, "    fn register(&mut self, ptr: usize) -> (u64, u64) {{").unwrap();
303    writeln!(out, "        self.counter += 1;").unwrap();
304    writeln!(out, "        let id = self.counter;").unwrap();
305    writeln!(out, "        let generation = id;").unwrap();
306    writeln!(out, "        self.entries.insert(id, HandleEntry {{ data: ptr, generation }});").unwrap();
307    writeln!(out, "        (id, generation)").unwrap();
308    writeln!(out, "    }}").unwrap();
309    writeln!(out, "    fn validate_handle(&self, id: u64, generation: u64) -> bool {{").unwrap();
310    writeln!(out, "        self.entries.get(&id).map_or(false, |e| e.generation == generation)").unwrap();
311    writeln!(out, "    }}").unwrap();
312    writeln!(out, "    fn deref(&self, id: u64) -> Option<usize> {{").unwrap();
313    writeln!(out, "        self.entries.get(&id).map(|e| e.data)").unwrap();
314    writeln!(out, "    }}").unwrap();
315    writeln!(out, "    fn free(&mut self, id: u64) -> Result<usize, ()> {{").unwrap();
316    writeln!(out, "        if let Some(entry) = self.entries.remove(&id) {{ Ok(entry.data) }} else {{ Err(()) }}").unwrap();
317    writeln!(out, "    }}").unwrap();
318    writeln!(out, "}}\n").unwrap();
319
320    writeln!(out, "fn logos_handle_registry() -> &'static std::sync::Mutex<HandleRegistry> {{").unwrap();
321    writeln!(out, "    use std::sync::OnceLock;").unwrap();
322    writeln!(out, "    static REGISTRY: OnceLock<std::sync::Mutex<HandleRegistry>> = OnceLock::new();").unwrap();
323    writeln!(out, "    REGISTRY.get_or_init(|| std::sync::Mutex::new(HandleRegistry::new()))").unwrap();
324    writeln!(out, "}}\n").unwrap();
325
326    out
327}
328
329/// Emit the opening of a catch_unwind panic boundary for an accessor function body.
330fn emit_catch_unwind_open(out: &mut String) {
331    writeln!(out, "    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {{").unwrap();
332}
333
334/// Emit the closing of a catch_unwind panic boundary for an accessor.
335/// `default_expr` is the fallback value on panic (e.g., "0", "std::ptr::null_mut()").
336fn emit_catch_unwind_close(out: &mut String, default_expr: &str) {
337    writeln!(out, "    }})) {{").unwrap();
338    writeln!(out, "        Ok(__v) => __v,").unwrap();
339    writeln!(out, "        Err(__panic) => {{").unwrap();
340    writeln!(out, "            let __msg = if let Some(s) = __panic.downcast_ref::<String>() {{ s.clone() }} else if let Some(s) = __panic.downcast_ref::<&str>() {{ s.to_string() }} else {{ \"Unknown panic\".to_string() }};").unwrap();
341    writeln!(out, "            logos_set_last_error(__msg);").unwrap();
342    writeln!(out, "            {}", default_expr).unwrap();
343    writeln!(out, "        }}").unwrap();
344    writeln!(out, "    }}").unwrap();
345}
346
347/// Emit a null handle check with early return. Used at the start of every accessor/free body.
348fn emit_null_handle_check(out: &mut String, default_expr: &str) {
349    writeln!(out, "        if handle.is_null() {{ logos_set_last_error(\"NullPointer: handle is null\".to_string()); return {}; }}", default_expr).unwrap();
350}
351
352/// Emit a null out-parameter check with early return. Used before every `*out = ...` write.
353fn emit_null_out_check(out: &mut String, default_expr: &str) {
354    writeln!(out, "        if out.is_null() {{ logos_set_last_error(\"NullPointer: output parameter is null\".to_string()); return {}; }}", default_expr).unwrap();
355}
356
357/// Emit registry handle lookup for an accessor. Returns the pointer or early-returns with error.
358fn emit_registry_deref(out: &mut String, default_expr: &str) {
359    writeln!(out, "        let __id = handle as u64;").unwrap();
360    writeln!(out, "        let __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
361    writeln!(out, "        let __ptr = match __reg.deref(__id) {{").unwrap();
362    writeln!(out, "            Some(p) => p,").unwrap();
363    writeln!(out, "            None => {{ logos_set_last_error(\"InvalidHandle: handle not found in registry\".to_string()); return {}; }}", default_expr).unwrap();
364    writeln!(out, "        }};").unwrap();
365    writeln!(out, "        drop(__reg);").unwrap();
366}
367
368/// Emit a _create body that registers the handle in the registry.
369/// `alloc_expr` is like `Vec::<i64>::new()`. `rust_type` is like `Vec<i64>`.
370fn emit_registry_create(out: &mut String, alloc_expr: &str, _rust_type: &str) {
371    writeln!(out, "        let __data = {};", alloc_expr).unwrap();
372    writeln!(out, "        let __ptr = Box::into_raw(Box::new(__data)) as usize;").unwrap();
373    writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
374    writeln!(out, "        let (__id, _) = __reg.register(__ptr);").unwrap();
375    writeln!(out, "        __id as LogosHandle").unwrap();
376}
377
378/// Emit a _free body that deregisters and drops the handle.
379/// `rust_type` is like `Vec<i64>`.
380fn emit_registry_free(out: &mut String, rust_type: &str) {
381    writeln!(out, "        if handle.is_null() {{ return; }}").unwrap();
382    writeln!(out, "        let __id = handle as u64;").unwrap();
383    writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
384    writeln!(out, "        match __reg.free(__id) {{").unwrap();
385    writeln!(out, "            Ok(__ptr) => {{ unsafe {{ drop(Box::from_raw(__ptr as *mut {})); }} }}", rust_type).unwrap();
386    writeln!(out, "            Err(()) => {{ logos_set_last_error(\"InvalidHandle: handle already freed or not found\".to_string()); }}").unwrap();
387    writeln!(out, "        }}").unwrap();
388}
389
390/// Generate accessor functions for a reference type (Seq, Map, Set, user structs).
391/// Returns the Rust source for the accessor/free functions.
392pub(crate) fn codegen_c_accessors(ty: &TypeExpr, interner: &Interner, registry: &TypeRegistry) -> String {
393    let mut out = String::new();
394    let mangled = mangle_type_for_c(ty, interner);
395
396    match ty {
397        TypeExpr::Generic { base, params } => {
398            let base_name = interner.resolve(*base);
399            match base_name {
400                "Seq" | "List" | "Vec" if !params.is_empty() => {
401                    let inner_rust_type = codegen_type_expr(&params[0], interner);
402                    let _inner_mangled = mangle_type_for_c(&params[0], interner);
403                    let is_inner_text = is_text_type(&params[0], interner);
404                    let vec_type = format!("LogosSeq<{}>", inner_rust_type);
405
406                    // len
407                    writeln!(out, "#[no_mangle]").unwrap();
408                    writeln!(out, "pub extern \"C\" fn logos_{}_len(handle: LogosHandle) -> usize {{", mangled).unwrap();
409                    emit_catch_unwind_open(&mut out);
410                    emit_null_handle_check(&mut out, "0");
411                    emit_registry_deref(&mut out, "0");
412                    writeln!(out, "        let seq = unsafe {{ &*(__ptr as *const {}) }};", vec_type).unwrap();
413                    writeln!(out, "        seq.len()").unwrap();
414                    emit_catch_unwind_close(&mut out, "0");
415                    writeln!(out, "}}\n").unwrap();
416
417                    // at (index access)
418                    if is_inner_text {
419                        writeln!(out, "#[no_mangle]").unwrap();
420                        writeln!(out, "pub extern \"C\" fn logos_{}_at(handle: LogosHandle, index: usize) -> *mut std::os::raw::c_char {{", mangled).unwrap();
421                        emit_catch_unwind_open(&mut out);
422                        emit_null_handle_check(&mut out, "std::ptr::null_mut()");
423                        emit_registry_deref(&mut out, "std::ptr::null_mut()");
424                        writeln!(out, "        let seq = unsafe {{ &*(__ptr as *const {}) }};", vec_type).unwrap();
425                        writeln!(out, "        if index >= seq.len() {{").unwrap();
426                        writeln!(out, "            logos_set_last_error(format!(\"Index {{}} out of bounds (len {{}})\", index, seq.len()));").unwrap();
427                        writeln!(out, "            return std::ptr::null_mut();").unwrap();
428                        writeln!(out, "        }}").unwrap();
429                        writeln!(out, "        match std::ffi::CString::new(seq.borrow()[index].clone()) {{").unwrap();
430                        writeln!(out, "            Ok(cstr) => cstr.into_raw(),").unwrap();
431                        writeln!(out, "            Err(_) => {{ logos_set_last_error(\"String contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
432                        writeln!(out, "        }}").unwrap();
433                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
434                        writeln!(out, "}}\n").unwrap();
435                    } else {
436                        writeln!(out, "#[no_mangle]").unwrap();
437                        writeln!(out, "pub extern \"C\" fn logos_{}_at(handle: LogosHandle, index: usize, out: *mut {}) -> LogosStatus {{", mangled, inner_rust_type).unwrap();
438                        emit_catch_unwind_open(&mut out);
439                        emit_null_handle_check(&mut out, "LogosStatus::NullPointer");
440                        emit_null_out_check(&mut out, "LogosStatus::NullPointer");
441                        emit_registry_deref(&mut out, "LogosStatus::InvalidHandle");
442                        writeln!(out, "        let seq = unsafe {{ &*(__ptr as *const {}) }};", vec_type).unwrap();
443                        writeln!(out, "        if index >= seq.len() {{").unwrap();
444                        writeln!(out, "            logos_set_last_error(format!(\"Index {{}} out of bounds (len {{}})\", index, seq.len()));").unwrap();
445                        writeln!(out, "            return LogosStatus::OutOfBounds;").unwrap();
446                        writeln!(out, "        }}").unwrap();
447                        writeln!(out, "        unsafe {{ *out = seq.borrow()[index].clone(); }}").unwrap();
448                        writeln!(out, "        LogosStatus::Ok").unwrap();
449                        emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
450                        writeln!(out, "}}\n").unwrap();
451                    }
452
453                    // create
454                    writeln!(out, "#[no_mangle]").unwrap();
455                    writeln!(out, "pub extern \"C\" fn logos_{}_create() -> LogosHandle {{", mangled).unwrap();
456                    emit_catch_unwind_open(&mut out);
457                    emit_registry_create(&mut out, &format!("LogosSeq::<{}>::new()", inner_rust_type), &vec_type);
458                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
459                    writeln!(out, "}}\n").unwrap();
460
461                    // push
462                    if is_inner_text {
463                        writeln!(out, "#[no_mangle]").unwrap();
464                        writeln!(out, "pub extern \"C\" fn logos_{}_push(handle: LogosHandle, value: *const std::os::raw::c_char) {{", mangled).unwrap();
465                        emit_catch_unwind_open(&mut out);
466                        emit_null_handle_check(&mut out, "()");
467                        emit_registry_deref(&mut out, "()");
468                        writeln!(out, "        let seq = unsafe {{ &mut *(__ptr as *mut {}) }};", vec_type).unwrap();
469                        writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
470                        writeln!(out, "        seq.push(val_str);").unwrap();
471                        emit_catch_unwind_close(&mut out, "()");
472                        writeln!(out, "}}\n").unwrap();
473                    } else {
474                        writeln!(out, "#[no_mangle]").unwrap();
475                        writeln!(out, "pub extern \"C\" fn logos_{}_push(handle: LogosHandle, value: {}) {{", mangled, inner_rust_type).unwrap();
476                        emit_catch_unwind_open(&mut out);
477                        emit_null_handle_check(&mut out, "()");
478                        emit_registry_deref(&mut out, "()");
479                        writeln!(out, "        let seq = unsafe {{ &mut *(__ptr as *mut {}) }};", vec_type).unwrap();
480                        writeln!(out, "        seq.push(value);").unwrap();
481                        emit_catch_unwind_close(&mut out, "()");
482                        writeln!(out, "}}\n").unwrap();
483                    }
484
485                    // pop
486                    if is_inner_text {
487                        writeln!(out, "#[no_mangle]").unwrap();
488                        writeln!(out, "pub extern \"C\" fn logos_{}_pop(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled).unwrap();
489                        emit_catch_unwind_open(&mut out);
490                        emit_null_handle_check(&mut out, "std::ptr::null_mut()");
491                        emit_registry_deref(&mut out, "std::ptr::null_mut()");
492                        writeln!(out, "        let seq = unsafe {{ &mut *(__ptr as *mut {}) }};", vec_type).unwrap();
493                        writeln!(out, "        match seq.pop() {{").unwrap();
494                        writeln!(out, "            Some(val) => match std::ffi::CString::new(val) {{").unwrap();
495                        writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
496                        writeln!(out, "                Err(_) => {{ logos_set_last_error(\"Value contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
497                        writeln!(out, "            }},").unwrap();
498                        writeln!(out, "            None => {{ logos_set_last_error(\"Pop from empty sequence\".to_string()); std::ptr::null_mut() }}").unwrap();
499                        writeln!(out, "        }}").unwrap();
500                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
501                        writeln!(out, "}}\n").unwrap();
502                    } else {
503                        writeln!(out, "#[no_mangle]").unwrap();
504                        writeln!(out, "pub extern \"C\" fn logos_{}_pop(handle: LogosHandle, out: *mut {}) -> LogosStatus {{", mangled, inner_rust_type).unwrap();
505                        emit_catch_unwind_open(&mut out);
506                        emit_null_handle_check(&mut out, "LogosStatus::NullPointer");
507                        emit_null_out_check(&mut out, "LogosStatus::NullPointer");
508                        emit_registry_deref(&mut out, "LogosStatus::InvalidHandle");
509                        writeln!(out, "        let seq = unsafe {{ &mut *(__ptr as *mut {}) }};", vec_type).unwrap();
510                        writeln!(out, "        match seq.pop() {{").unwrap();
511                        writeln!(out, "            Some(val) => {{ unsafe {{ *out = val; }} LogosStatus::Ok }}").unwrap();
512                        writeln!(out, "            None => {{ logos_set_last_error(\"Pop from empty sequence\".to_string()); LogosStatus::Error }}").unwrap();
513                        writeln!(out, "        }}").unwrap();
514                        emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
515                        writeln!(out, "}}\n").unwrap();
516                    }
517
518                    // to_json
519                    writeln!(out, "#[no_mangle]").unwrap();
520                    writeln!(out, "pub extern \"C\" fn logos_{}_to_json(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled).unwrap();
521                    emit_catch_unwind_open(&mut out);
522                    emit_null_handle_check(&mut out, "std::ptr::null_mut()");
523                    emit_registry_deref(&mut out, "std::ptr::null_mut()");
524                    writeln!(out, "        let seq = unsafe {{ &*(__ptr as *const {}) }};", vec_type).unwrap();
525                    writeln!(out, "        match serde_json::to_string(seq) {{").unwrap();
526                    writeln!(out, "            Ok(json) => match std::ffi::CString::new(json) {{").unwrap();
527                    writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
528                    writeln!(out, "                Err(_) => {{ logos_set_last_error(\"JSON contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
529                    writeln!(out, "            }},").unwrap();
530                    writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); std::ptr::null_mut() }}").unwrap();
531                    writeln!(out, "        }}").unwrap();
532                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
533                    writeln!(out, "}}\n").unwrap();
534
535                    // from_json
536                    writeln!(out, "#[no_mangle]").unwrap();
537                    writeln!(out, "pub extern \"C\" fn logos_{}_from_json(json: *const std::os::raw::c_char, out: *mut LogosHandle) -> LogosStatus {{", mangled).unwrap();
538                    emit_catch_unwind_open(&mut out);
539                    writeln!(out, "        if json.is_null() {{ logos_set_last_error(\"Null JSON pointer\".to_string()); return LogosStatus::NullPointer; }}").unwrap();
540                    emit_null_out_check(&mut out, "LogosStatus::NullPointer");
541                    writeln!(out, "        let json_str = unsafe {{ std::ffi::CStr::from_ptr(json).to_string_lossy() }};").unwrap();
542                    writeln!(out, "        match serde_json::from_str::<{}>(&json_str) {{", vec_type).unwrap();
543                    writeln!(out, "            Ok(val) => {{").unwrap();
544                    writeln!(out, "                let __ptr = Box::into_raw(Box::new(val)) as usize;").unwrap();
545                    writeln!(out, "                let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
546                    writeln!(out, "                let (__id, _) = __reg.register(__ptr);").unwrap();
547                    writeln!(out, "                unsafe {{ *out = __id as LogosHandle; }}").unwrap();
548                    writeln!(out, "                LogosStatus::Ok").unwrap();
549                    writeln!(out, "            }}").unwrap();
550                    writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); LogosStatus::DeserializationFailed }}").unwrap();
551                    writeln!(out, "        }}").unwrap();
552                    emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
553                    writeln!(out, "}}\n").unwrap();
554
555                    // free
556                    writeln!(out, "#[no_mangle]").unwrap();
557                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled).unwrap();
558                    emit_catch_unwind_open(&mut out);
559                    emit_registry_free(&mut out, &vec_type);
560                    emit_catch_unwind_close(&mut out, "()");
561                    writeln!(out, "}}\n").unwrap();
562                }
563
564                "Map" | "HashMap" if params.len() >= 2 => {
565                    let key_rust = codegen_type_expr(&params[0], interner);
566                    let val_rust = codegen_type_expr(&params[1], interner);
567                    let is_key_text = is_text_type(&params[0], interner);
568                    let is_val_text = is_text_type(&params[1], interner);
569                    let map_type = format!("LogosMap<{}, {}>", key_rust, val_rust);
570
571                    // len
572                    writeln!(out, "#[no_mangle]").unwrap();
573                    writeln!(out, "pub extern \"C\" fn logos_{}_len(handle: LogosHandle) -> usize {{", mangled).unwrap();
574                    emit_catch_unwind_open(&mut out);
575                    emit_null_handle_check(&mut out, "0");
576                    emit_registry_deref(&mut out, "0");
577                    writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
578                    writeln!(out, "        map.len()").unwrap();
579                    emit_catch_unwind_close(&mut out, "0");
580                    writeln!(out, "}}\n").unwrap();
581
582                    // get
583                    if is_key_text {
584                        if is_val_text {
585                            writeln!(out, "#[no_mangle]").unwrap();
586                            writeln!(out, "pub extern \"C\" fn logos_{}_get(handle: LogosHandle, key: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {{", mangled).unwrap();
587                            emit_catch_unwind_open(&mut out);
588                            emit_null_handle_check(&mut out, "std::ptr::null_mut()");
589                            emit_registry_deref(&mut out, "std::ptr::null_mut()");
590                            writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
591                            writeln!(out, "        let key_str = unsafe {{ std::ffi::CStr::from_ptr(key).to_string_lossy().into_owned() }};").unwrap();
592                            writeln!(out, "        match map.get(&key_str) {{").unwrap();
593                            writeln!(out, "            Some(val) => match std::ffi::CString::new(val.clone()) {{").unwrap();
594                            writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
595                            writeln!(out, "                Err(_) => {{ logos_set_last_error(\"Value contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
596                            writeln!(out, "            }},").unwrap();
597                            writeln!(out, "            None => std::ptr::null_mut(),").unwrap();
598                            writeln!(out, "        }}").unwrap();
599                            emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
600                            writeln!(out, "}}\n").unwrap();
601                        } else {
602                            writeln!(out, "#[no_mangle]").unwrap();
603                            writeln!(out, "pub extern \"C\" fn logos_{}_get(handle: LogosHandle, key: *const std::os::raw::c_char, out: *mut {}) -> LogosStatus {{", mangled, val_rust).unwrap();
604                            emit_catch_unwind_open(&mut out);
605                            emit_null_handle_check(&mut out, "LogosStatus::NullPointer");
606                            emit_null_out_check(&mut out, "LogosStatus::NullPointer");
607                            emit_registry_deref(&mut out, "LogosStatus::InvalidHandle");
608                            writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
609                            writeln!(out, "        let key_str = unsafe {{ std::ffi::CStr::from_ptr(key).to_string_lossy().into_owned() }};").unwrap();
610                            writeln!(out, "        match map.get(&key_str) {{").unwrap();
611                            writeln!(out, "            Some(val) => {{ unsafe {{ *out = val.clone(); }} LogosStatus::Ok }}").unwrap();
612                            writeln!(out, "            None => {{ logos_set_last_error(format!(\"Key not found: {{}}\", key_str)); LogosStatus::Error }}").unwrap();
613                            writeln!(out, "        }}").unwrap();
614                            emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
615                            writeln!(out, "}}\n").unwrap();
616                        }
617                    } else {
618                        if is_val_text {
619                            writeln!(out, "#[no_mangle]").unwrap();
620                            writeln!(out, "pub extern \"C\" fn logos_{}_get(handle: LogosHandle, key: {}) -> *mut std::os::raw::c_char {{", mangled, key_rust).unwrap();
621                            emit_catch_unwind_open(&mut out);
622                            emit_null_handle_check(&mut out, "std::ptr::null_mut()");
623                            emit_registry_deref(&mut out, "std::ptr::null_mut()");
624                            writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
625                            writeln!(out, "        match map.get(&key) {{").unwrap();
626                            writeln!(out, "            Some(val) => match std::ffi::CString::new(val.clone()) {{").unwrap();
627                            writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
628                            writeln!(out, "                Err(_) => {{ logos_set_last_error(\"Value contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
629                            writeln!(out, "            }},").unwrap();
630                            writeln!(out, "            None => std::ptr::null_mut(),").unwrap();
631                            writeln!(out, "        }}").unwrap();
632                            emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
633                            writeln!(out, "}}\n").unwrap();
634                        } else {
635                            writeln!(out, "#[no_mangle]").unwrap();
636                            writeln!(out, "pub extern \"C\" fn logos_{}_get(handle: LogosHandle, key: {}, out: *mut {}) -> LogosStatus {{", mangled, key_rust, val_rust).unwrap();
637                            emit_catch_unwind_open(&mut out);
638                            emit_null_handle_check(&mut out, "LogosStatus::NullPointer");
639                            emit_null_out_check(&mut out, "LogosStatus::NullPointer");
640                            emit_registry_deref(&mut out, "LogosStatus::InvalidHandle");
641                            writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
642                            writeln!(out, "        match map.get(&key) {{").unwrap();
643                            writeln!(out, "            Some(val) => {{ unsafe {{ *out = val.clone(); }} LogosStatus::Ok }}").unwrap();
644                            writeln!(out, "            None => {{ logos_set_last_error(format!(\"Key not found: {{}}\", key)); LogosStatus::Error }}").unwrap();
645                            writeln!(out, "        }}").unwrap();
646                            emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
647                            writeln!(out, "}}\n").unwrap();
648                        }
649                    }
650
651                    // keys
652                    writeln!(out, "#[no_mangle]").unwrap();
653                    writeln!(out, "pub extern \"C\" fn logos_{}_keys(handle: LogosHandle) -> LogosHandle {{", mangled).unwrap();
654                    emit_catch_unwind_open(&mut out);
655                    emit_null_handle_check(&mut out, "std::ptr::null_mut() as LogosHandle");
656                    emit_registry_deref(&mut out, "std::ptr::null_mut() as LogosHandle");
657                    writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
658                    writeln!(out, "        let keys = LogosSeq::from_vec(map.borrow().keys().cloned().collect::<Vec<{}>>());", key_rust).unwrap();
659                    writeln!(out, "        let __kptr = Box::into_raw(Box::new(keys)) as usize;").unwrap();
660                    writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
661                    writeln!(out, "        let (__id, _) = __reg.register(__kptr);").unwrap();
662                    writeln!(out, "        __id as LogosHandle").unwrap();
663                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
664                    writeln!(out, "}}\n").unwrap();
665
666                    // values
667                    writeln!(out, "#[no_mangle]").unwrap();
668                    writeln!(out, "pub extern \"C\" fn logos_{}_values(handle: LogosHandle) -> LogosHandle {{", mangled).unwrap();
669                    emit_catch_unwind_open(&mut out);
670                    emit_null_handle_check(&mut out, "std::ptr::null_mut() as LogosHandle");
671                    emit_registry_deref(&mut out, "std::ptr::null_mut() as LogosHandle");
672                    writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
673                    writeln!(out, "        let values = LogosSeq::from_vec(map.borrow().values().cloned().collect::<Vec<{}>>());", val_rust).unwrap();
674                    writeln!(out, "        let __vptr = Box::into_raw(Box::new(values)) as usize;").unwrap();
675                    writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
676                    writeln!(out, "        let (__id, _) = __reg.register(__vptr);").unwrap();
677                    writeln!(out, "        __id as LogosHandle").unwrap();
678                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
679                    writeln!(out, "}}\n").unwrap();
680
681                    // create
682                    writeln!(out, "#[no_mangle]").unwrap();
683                    writeln!(out, "pub extern \"C\" fn logos_{}_create() -> LogosHandle {{", mangled).unwrap();
684                    emit_catch_unwind_open(&mut out);
685                    emit_registry_create(&mut out, &format!("LogosMap::<{}, {}>::new()", key_rust, val_rust), &map_type);
686                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
687                    writeln!(out, "}}\n").unwrap();
688
689                    // insert
690                    if is_key_text {
691                        let val_param = if is_val_text {
692                            "value: *const std::os::raw::c_char".to_string()
693                        } else {
694                            format!("value: {}", val_rust)
695                        };
696                        writeln!(out, "#[no_mangle]").unwrap();
697                        writeln!(out, "pub extern \"C\" fn logos_{}_insert(handle: LogosHandle, key: *const std::os::raw::c_char, {}) {{", mangled, val_param).unwrap();
698                        emit_catch_unwind_open(&mut out);
699                        emit_null_handle_check(&mut out, "()");
700                        emit_registry_deref(&mut out, "()");
701                        writeln!(out, "        let map = unsafe {{ &mut *(__ptr as *mut {}) }};", map_type).unwrap();
702                        writeln!(out, "        let key_str = unsafe {{ std::ffi::CStr::from_ptr(key).to_string_lossy().into_owned() }};").unwrap();
703                        if is_val_text {
704                            writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
705                            writeln!(out, "        map.insert(key_str, val_str);").unwrap();
706                        } else {
707                            writeln!(out, "        map.insert(key_str, value);").unwrap();
708                        }
709                        emit_catch_unwind_close(&mut out, "()");
710                        writeln!(out, "}}\n").unwrap();
711                    } else {
712                        let val_param = if is_val_text {
713                            "value: *const std::os::raw::c_char".to_string()
714                        } else {
715                            format!("value: {}", val_rust)
716                        };
717                        writeln!(out, "#[no_mangle]").unwrap();
718                        writeln!(out, "pub extern \"C\" fn logos_{}_insert(handle: LogosHandle, key: {}, {}) {{", mangled, key_rust, val_param).unwrap();
719                        emit_catch_unwind_open(&mut out);
720                        emit_null_handle_check(&mut out, "()");
721                        emit_registry_deref(&mut out, "()");
722                        writeln!(out, "        let map = unsafe {{ &mut *(__ptr as *mut {}) }};", map_type).unwrap();
723                        if is_val_text {
724                            writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
725                            writeln!(out, "        map.insert(key, val_str);").unwrap();
726                        } else {
727                            writeln!(out, "        map.insert(key, value);").unwrap();
728                        }
729                        emit_catch_unwind_close(&mut out, "()");
730                        writeln!(out, "}}\n").unwrap();
731                    }
732
733                    // remove
734                    if is_key_text {
735                        writeln!(out, "#[no_mangle]").unwrap();
736                        writeln!(out, "pub extern \"C\" fn logos_{}_remove(handle: LogosHandle, key: *const std::os::raw::c_char) -> bool {{", mangled).unwrap();
737                        emit_catch_unwind_open(&mut out);
738                        emit_null_handle_check(&mut out, "false");
739                        emit_registry_deref(&mut out, "false");
740                        writeln!(out, "        let map = unsafe {{ &mut *(__ptr as *mut {}) }};", map_type).unwrap();
741                        writeln!(out, "        let key_str = unsafe {{ std::ffi::CStr::from_ptr(key).to_string_lossy().into_owned() }};").unwrap();
742                        writeln!(out, "        map.remove(&key_str).is_some()").unwrap();
743                        emit_catch_unwind_close(&mut out, "false");
744                        writeln!(out, "}}\n").unwrap();
745                    } else {
746                        writeln!(out, "#[no_mangle]").unwrap();
747                        writeln!(out, "pub extern \"C\" fn logos_{}_remove(handle: LogosHandle, key: {}) -> bool {{", mangled, key_rust).unwrap();
748                        emit_catch_unwind_open(&mut out);
749                        emit_null_handle_check(&mut out, "false");
750                        emit_registry_deref(&mut out, "false");
751                        writeln!(out, "        let map = unsafe {{ &mut *(__ptr as *mut {}) }};", map_type).unwrap();
752                        writeln!(out, "        map.remove(&key).is_some()").unwrap();
753                        emit_catch_unwind_close(&mut out, "false");
754                        writeln!(out, "}}\n").unwrap();
755                    }
756
757                    // to_json
758                    writeln!(out, "#[no_mangle]").unwrap();
759                    writeln!(out, "pub extern \"C\" fn logos_{}_to_json(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled).unwrap();
760                    emit_catch_unwind_open(&mut out);
761                    emit_null_handle_check(&mut out, "std::ptr::null_mut()");
762                    emit_registry_deref(&mut out, "std::ptr::null_mut()");
763                    writeln!(out, "        let map = unsafe {{ &*(__ptr as *const {}) }};", map_type).unwrap();
764                    writeln!(out, "        match serde_json::to_string(map) {{").unwrap();
765                    writeln!(out, "            Ok(json) => match std::ffi::CString::new(json) {{").unwrap();
766                    writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
767                    writeln!(out, "                Err(_) => {{ logos_set_last_error(\"JSON contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
768                    writeln!(out, "            }},").unwrap();
769                    writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); std::ptr::null_mut() }}").unwrap();
770                    writeln!(out, "        }}").unwrap();
771                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
772                    writeln!(out, "}}\n").unwrap();
773
774                    // from_json
775                    writeln!(out, "#[no_mangle]").unwrap();
776                    writeln!(out, "pub extern \"C\" fn logos_{}_from_json(json: *const std::os::raw::c_char, out: *mut LogosHandle) -> LogosStatus {{", mangled).unwrap();
777                    emit_catch_unwind_open(&mut out);
778                    writeln!(out, "        if json.is_null() {{ logos_set_last_error(\"Null JSON pointer\".to_string()); return LogosStatus::NullPointer; }}").unwrap();
779                    emit_null_out_check(&mut out, "LogosStatus::NullPointer");
780                    writeln!(out, "        let json_str = unsafe {{ std::ffi::CStr::from_ptr(json).to_string_lossy() }};").unwrap();
781                    writeln!(out, "        match serde_json::from_str::<{}>(&json_str) {{", map_type).unwrap();
782                    writeln!(out, "            Ok(val) => {{").unwrap();
783                    writeln!(out, "                let __ptr = Box::into_raw(Box::new(val)) as usize;").unwrap();
784                    writeln!(out, "                let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
785                    writeln!(out, "                let (__id, _) = __reg.register(__ptr);").unwrap();
786                    writeln!(out, "                unsafe {{ *out = __id as LogosHandle; }}").unwrap();
787                    writeln!(out, "                LogosStatus::Ok").unwrap();
788                    writeln!(out, "            }}").unwrap();
789                    writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); LogosStatus::DeserializationFailed }}").unwrap();
790                    writeln!(out, "        }}").unwrap();
791                    emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
792                    writeln!(out, "}}\n").unwrap();
793
794                    // free
795                    writeln!(out, "#[no_mangle]").unwrap();
796                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled).unwrap();
797                    emit_catch_unwind_open(&mut out);
798                    emit_registry_free(&mut out, &map_type);
799                    emit_catch_unwind_close(&mut out, "()");
800                    writeln!(out, "}}\n").unwrap();
801                }
802
803                "Set" | "HashSet" if !params.is_empty() => {
804                    let inner_rust_type = codegen_type_expr(&params[0], interner);
805                    let is_inner_text = is_text_type(&params[0], interner);
806                    let set_type = format!("Set<{}>", inner_rust_type);
807
808                    // len
809                    writeln!(out, "#[no_mangle]").unwrap();
810                    writeln!(out, "pub extern \"C\" fn logos_{}_len(handle: LogosHandle) -> usize {{", mangled).unwrap();
811                    emit_catch_unwind_open(&mut out);
812                    emit_null_handle_check(&mut out, "0");
813                    emit_registry_deref(&mut out, "0");
814                    writeln!(out, "        let set = unsafe {{ &*(__ptr as *const {}) }};", set_type).unwrap();
815                    writeln!(out, "        set.len()").unwrap();
816                    emit_catch_unwind_close(&mut out, "0");
817                    writeln!(out, "}}\n").unwrap();
818
819                    // contains
820                    if is_inner_text {
821                        writeln!(out, "#[no_mangle]").unwrap();
822                        writeln!(out, "pub extern \"C\" fn logos_{}_contains(handle: LogosHandle, value: *const std::os::raw::c_char) -> bool {{", mangled).unwrap();
823                        emit_catch_unwind_open(&mut out);
824                        emit_null_handle_check(&mut out, "false");
825                        emit_registry_deref(&mut out, "false");
826                        writeln!(out, "        let set = unsafe {{ &*(__ptr as *const {}) }};", set_type).unwrap();
827                        writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
828                        writeln!(out, "        set.contains(&val_str)").unwrap();
829                        emit_catch_unwind_close(&mut out, "false");
830                        writeln!(out, "}}\n").unwrap();
831                    } else {
832                        writeln!(out, "#[no_mangle]").unwrap();
833                        writeln!(out, "pub extern \"C\" fn logos_{}_contains(handle: LogosHandle, value: {}) -> bool {{", mangled, inner_rust_type).unwrap();
834                        emit_catch_unwind_open(&mut out);
835                        emit_null_handle_check(&mut out, "false");
836                        emit_registry_deref(&mut out, "false");
837                        writeln!(out, "        let set = unsafe {{ &*(__ptr as *const {}) }};", set_type).unwrap();
838                        writeln!(out, "        set.contains(&value)").unwrap();
839                        emit_catch_unwind_close(&mut out, "false");
840                        writeln!(out, "}}\n").unwrap();
841                    }
842
843                    // create
844                    writeln!(out, "#[no_mangle]").unwrap();
845                    writeln!(out, "pub extern \"C\" fn logos_{}_create() -> LogosHandle {{", mangled).unwrap();
846                    emit_catch_unwind_open(&mut out);
847                    emit_registry_create(&mut out, &format!("Set::<{}>::default()", inner_rust_type), &set_type);
848                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
849                    writeln!(out, "}}\n").unwrap();
850
851                    // insert
852                    if is_inner_text {
853                        writeln!(out, "#[no_mangle]").unwrap();
854                        writeln!(out, "pub extern \"C\" fn logos_{}_insert(handle: LogosHandle, value: *const std::os::raw::c_char) {{", mangled).unwrap();
855                        emit_catch_unwind_open(&mut out);
856                        emit_null_handle_check(&mut out, "()");
857                        emit_registry_deref(&mut out, "()");
858                        writeln!(out, "        let set = unsafe {{ &mut *(__ptr as *mut {}) }};", set_type).unwrap();
859                        writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
860                        writeln!(out, "        set.insert(val_str);").unwrap();
861                        emit_catch_unwind_close(&mut out, "()");
862                        writeln!(out, "}}\n").unwrap();
863                    } else {
864                        writeln!(out, "#[no_mangle]").unwrap();
865                        writeln!(out, "pub extern \"C\" fn logos_{}_insert(handle: LogosHandle, value: {}) {{", mangled, inner_rust_type).unwrap();
866                        emit_catch_unwind_open(&mut out);
867                        emit_null_handle_check(&mut out, "()");
868                        emit_registry_deref(&mut out, "()");
869                        writeln!(out, "        let set = unsafe {{ &mut *(__ptr as *mut {}) }};", set_type).unwrap();
870                        writeln!(out, "        set.insert(value);").unwrap();
871                        emit_catch_unwind_close(&mut out, "()");
872                        writeln!(out, "}}\n").unwrap();
873                    }
874
875                    // remove
876                    if is_inner_text {
877                        writeln!(out, "#[no_mangle]").unwrap();
878                        writeln!(out, "pub extern \"C\" fn logos_{}_remove(handle: LogosHandle, value: *const std::os::raw::c_char) -> bool {{", mangled).unwrap();
879                        emit_catch_unwind_open(&mut out);
880                        emit_null_handle_check(&mut out, "false");
881                        emit_registry_deref(&mut out, "false");
882                        writeln!(out, "        let set = unsafe {{ &mut *(__ptr as *mut {}) }};", set_type).unwrap();
883                        writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
884                        writeln!(out, "        set.remove(&val_str)").unwrap();
885                        emit_catch_unwind_close(&mut out, "false");
886                        writeln!(out, "}}\n").unwrap();
887                    } else {
888                        writeln!(out, "#[no_mangle]").unwrap();
889                        writeln!(out, "pub extern \"C\" fn logos_{}_remove(handle: LogosHandle, value: {}) -> bool {{", mangled, inner_rust_type).unwrap();
890                        emit_catch_unwind_open(&mut out);
891                        emit_null_handle_check(&mut out, "false");
892                        emit_registry_deref(&mut out, "false");
893                        writeln!(out, "        let set = unsafe {{ &mut *(__ptr as *mut {}) }};", set_type).unwrap();
894                        writeln!(out, "        set.remove(&value)").unwrap();
895                        emit_catch_unwind_close(&mut out, "false");
896                        writeln!(out, "}}\n").unwrap();
897                    }
898
899                    // to_json
900                    writeln!(out, "#[no_mangle]").unwrap();
901                    writeln!(out, "pub extern \"C\" fn logos_{}_to_json(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled).unwrap();
902                    emit_catch_unwind_open(&mut out);
903                    emit_null_handle_check(&mut out, "std::ptr::null_mut()");
904                    emit_registry_deref(&mut out, "std::ptr::null_mut()");
905                    writeln!(out, "        let set = unsafe {{ &*(__ptr as *const {}) }};", set_type).unwrap();
906                    writeln!(out, "        match serde_json::to_string(set) {{").unwrap();
907                    writeln!(out, "            Ok(json) => match std::ffi::CString::new(json) {{").unwrap();
908                    writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
909                    writeln!(out, "                Err(_) => {{ logos_set_last_error(\"JSON contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
910                    writeln!(out, "            }},").unwrap();
911                    writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); std::ptr::null_mut() }}").unwrap();
912                    writeln!(out, "        }}").unwrap();
913                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
914                    writeln!(out, "}}\n").unwrap();
915
916                    // free
917                    writeln!(out, "#[no_mangle]").unwrap();
918                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled).unwrap();
919                    emit_catch_unwind_open(&mut out);
920                    emit_registry_free(&mut out, &set_type);
921                    emit_catch_unwind_close(&mut out, "()");
922                    writeln!(out, "}}\n").unwrap();
923                }
924
925                "Option" | "Maybe" if !params.is_empty() => {
926                    let inner_rust_type = codegen_type_expr(&params[0], interner);
927                    let is_inner_text = is_text_type(&params[0], interner);
928                    let opt_type = format!("Option<{}>", inner_rust_type);
929
930                    // is_some
931                    writeln!(out, "#[no_mangle]").unwrap();
932                    writeln!(out, "pub extern \"C\" fn logos_{}_is_some(handle: LogosHandle) -> bool {{", mangled).unwrap();
933                    emit_catch_unwind_open(&mut out);
934                    emit_null_handle_check(&mut out, "false");
935                    emit_registry_deref(&mut out, "false");
936                    writeln!(out, "        let opt = unsafe {{ &*(__ptr as *const {}) }};", opt_type).unwrap();
937                    writeln!(out, "        opt.is_some()").unwrap();
938                    emit_catch_unwind_close(&mut out, "false");
939                    writeln!(out, "}}\n").unwrap();
940
941                    // unwrap
942                    if is_inner_text {
943                        writeln!(out, "#[no_mangle]").unwrap();
944                        writeln!(out, "pub extern \"C\" fn logos_{}_unwrap(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled).unwrap();
945                        emit_catch_unwind_open(&mut out);
946                        emit_null_handle_check(&mut out, "std::ptr::null_mut()");
947                        emit_registry_deref(&mut out, "std::ptr::null_mut()");
948                        writeln!(out, "        let opt = unsafe {{ &*(__ptr as *const {}) }};", opt_type).unwrap();
949                        writeln!(out, "        match opt {{").unwrap();
950                        writeln!(out, "            Some(val) => match std::ffi::CString::new(val.clone()) {{").unwrap();
951                        writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
952                        writeln!(out, "                Err(_) => {{ logos_set_last_error(\"Value contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
953                        writeln!(out, "            }},").unwrap();
954                        writeln!(out, "            None => {{ logos_set_last_error(\"Unwrap called on None\".to_string()); std::ptr::null_mut() }}").unwrap();
955                        writeln!(out, "        }}").unwrap();
956                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
957                        writeln!(out, "}}\n").unwrap();
958                    } else {
959                        writeln!(out, "#[no_mangle]").unwrap();
960                        writeln!(out, "pub extern \"C\" fn logos_{}_unwrap(handle: LogosHandle, out: *mut {}) -> LogosStatus {{", mangled, inner_rust_type).unwrap();
961                        emit_catch_unwind_open(&mut out);
962                        emit_null_handle_check(&mut out, "LogosStatus::NullPointer");
963                        emit_null_out_check(&mut out, "LogosStatus::NullPointer");
964                        emit_registry_deref(&mut out, "LogosStatus::InvalidHandle");
965                        writeln!(out, "        let opt = unsafe {{ &*(__ptr as *const {}) }};", opt_type).unwrap();
966                        writeln!(out, "        match opt {{").unwrap();
967                        writeln!(out, "            Some(val) => {{ unsafe {{ *out = val.clone(); }} LogosStatus::Ok }}").unwrap();
968                        writeln!(out, "            None => {{ logos_set_last_error(\"Unwrap called on None\".to_string()); LogosStatus::Error }}").unwrap();
969                        writeln!(out, "        }}").unwrap();
970                        emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
971                        writeln!(out, "}}\n").unwrap();
972                    }
973
974                    // create_some
975                    if is_inner_text {
976                        writeln!(out, "#[no_mangle]").unwrap();
977                        writeln!(out, "pub extern \"C\" fn logos_{}_some(value: *const std::os::raw::c_char) -> LogosHandle {{", mangled).unwrap();
978                        emit_catch_unwind_open(&mut out);
979                        writeln!(out, "        if value.is_null() {{ logos_set_last_error(\"NullPointer: value is null\".to_string()); return std::ptr::null_mut() as LogosHandle; }}").unwrap();
980                        writeln!(out, "        let val_str = unsafe {{ std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned() }};").unwrap();
981                        writeln!(out, "        let opt: {} = Some(val_str);", opt_type).unwrap();
982                        writeln!(out, "        let __ptr = Box::into_raw(Box::new(opt)) as usize;").unwrap();
983                        writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
984                        writeln!(out, "        let (__id, _) = __reg.register(__ptr);").unwrap();
985                        writeln!(out, "        __id as LogosHandle").unwrap();
986                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
987                        writeln!(out, "}}\n").unwrap();
988                    } else {
989                        writeln!(out, "#[no_mangle]").unwrap();
990                        writeln!(out, "pub extern \"C\" fn logos_{}_some(value: {}) -> LogosHandle {{", mangled, inner_rust_type).unwrap();
991                        emit_catch_unwind_open(&mut out);
992                        writeln!(out, "        let opt: {} = Some(value);", opt_type).unwrap();
993                        writeln!(out, "        let __ptr = Box::into_raw(Box::new(opt)) as usize;").unwrap();
994                        writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
995                        writeln!(out, "        let (__id, _) = __reg.register(__ptr);").unwrap();
996                        writeln!(out, "        __id as LogosHandle").unwrap();
997                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
998                        writeln!(out, "}}\n").unwrap();
999                    }
1000
1001                    // create_none
1002                    writeln!(out, "#[no_mangle]").unwrap();
1003                    writeln!(out, "pub extern \"C\" fn logos_{}_none() -> LogosHandle {{", mangled).unwrap();
1004                    emit_catch_unwind_open(&mut out);
1005                    writeln!(out, "        let opt: {} = None;", opt_type).unwrap();
1006                    writeln!(out, "        let __ptr = Box::into_raw(Box::new(opt)) as usize;").unwrap();
1007                    writeln!(out, "        let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
1008                    writeln!(out, "        let (__id, _) = __reg.register(__ptr);").unwrap();
1009                    writeln!(out, "        __id as LogosHandle").unwrap();
1010                    emit_catch_unwind_close(&mut out, "std::ptr::null_mut() as LogosHandle");
1011                    writeln!(out, "}}\n").unwrap();
1012
1013                    // free
1014                    writeln!(out, "#[no_mangle]").unwrap();
1015                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled).unwrap();
1016                    emit_catch_unwind_open(&mut out);
1017                    emit_registry_free(&mut out, &opt_type);
1018                    emit_catch_unwind_close(&mut out, "()");
1019                    writeln!(out, "}}\n").unwrap();
1020                }
1021
1022                _ => {}
1023            }
1024        }
1025        TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
1026            let type_name = interner.resolve(*sym);
1027            let type_def = registry.get(*sym);
1028
1029            match type_def {
1030                Some(TypeDef::Struct { fields, is_portable, .. }) => {
1031                    let mangled_struct = type_name.to_lowercase();
1032                    let rust_struct_name = type_name.to_string();
1033
1034                    for field in fields {
1035                        let field_name = interner.resolve(field.name);
1036                        let is_field_text = match &field.ty {
1037                            FieldType::Primitive(s) | FieldType::Named(s) => {
1038                                let n = interner.resolve(*s);
1039                                n == "Text" || n == "String"
1040                            }
1041                            _ => false,
1042                        };
1043
1044                        writeln!(out, "#[no_mangle]").unwrap();
1045                        if is_field_text {
1046                            writeln!(out, "pub extern \"C\" fn logos_{}_{field}(handle: LogosHandle) -> *mut std::os::raw::c_char {{",
1047                                mangled_struct, field = field_name).unwrap();
1048                            emit_catch_unwind_open(&mut out);
1049                            emit_null_handle_check(&mut out, "std::ptr::null_mut()");
1050                            emit_registry_deref(&mut out, "std::ptr::null_mut()");
1051                            writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_struct_name).unwrap();
1052                            writeln!(out, "        match std::ffi::CString::new(obj.{}.clone()) {{", field_name).unwrap();
1053                            writeln!(out, "            Ok(cstr) => cstr.into_raw(),").unwrap();
1054                            writeln!(out, "            Err(_) => {{ logos_set_last_error(\"Field contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
1055                            writeln!(out, "        }}").unwrap();
1056                            emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
1057                            writeln!(out, "}}\n").unwrap();
1058                        } else {
1059                            let (field_rust_type, is_char) = match &field.ty {
1060                                FieldType::Primitive(s) | FieldType::Named(s) => {
1061                                    let n = interner.resolve(*s);
1062                                    match n {
1063                                        "Int" => ("i64", false),
1064                                        "Nat" => ("u64", false),
1065                                        "Real" | "Float" => ("f64", false),
1066                                        "Bool" | "Boolean" => ("bool", false),
1067                                        "Byte" => ("u8", false),
1068                                        "Char" => ("u32", true),
1069                                        _ => (n, false),
1070                                    }
1071                                }
1072                                _ => ("LogosHandle", false),
1073                            };
1074                            writeln!(out, "pub extern \"C\" fn logos_{}_{field}(handle: LogosHandle) -> {} {{",
1075                                mangled_struct, field_rust_type, field = field_name).unwrap();
1076                            emit_catch_unwind_open(&mut out);
1077                            emit_null_handle_check(&mut out, "Default::default()");
1078                            emit_registry_deref(&mut out, "Default::default()");
1079                            writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_struct_name).unwrap();
1080                            if is_char {
1081                                writeln!(out, "        obj.{}.clone() as u32", field_name).unwrap();
1082                            } else {
1083                                writeln!(out, "        obj.{}.clone()", field_name).unwrap();
1084                            }
1085                            emit_catch_unwind_close(&mut out, "Default::default()");
1086                            writeln!(out, "}}\n").unwrap();
1087                        }
1088                    }
1089
1090                    {
1091                        // to_json / from_json — always generated for C export reference-type structs
1092                        writeln!(out, "#[no_mangle]").unwrap();
1093                        writeln!(out, "pub extern \"C\" fn logos_{}_to_json(handle: LogosHandle) -> *mut std::os::raw::c_char {{", mangled_struct).unwrap();
1094                        emit_catch_unwind_open(&mut out);
1095                        emit_null_handle_check(&mut out, "std::ptr::null_mut()");
1096                        emit_registry_deref(&mut out, "std::ptr::null_mut()");
1097                        writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_struct_name).unwrap();
1098                        writeln!(out, "        match serde_json::to_string(obj) {{").unwrap();
1099                        writeln!(out, "            Ok(json) => match std::ffi::CString::new(json) {{").unwrap();
1100                        writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
1101                        writeln!(out, "                Err(_) => {{ logos_set_last_error(\"JSON contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
1102                        writeln!(out, "            }},").unwrap();
1103                        writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); std::ptr::null_mut() }}").unwrap();
1104                        writeln!(out, "        }}").unwrap();
1105                        emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
1106                        writeln!(out, "}}\n").unwrap();
1107
1108                        // from_json — uses registry to register the deserialized handle
1109                        writeln!(out, "#[no_mangle]").unwrap();
1110                        writeln!(out, "pub extern \"C\" fn logos_{}_from_json(json: *const std::os::raw::c_char, out: *mut LogosHandle) -> LogosStatus {{", mangled_struct).unwrap();
1111                        emit_catch_unwind_open(&mut out);
1112                        writeln!(out, "        if json.is_null() {{ logos_set_last_error(\"Null JSON pointer\".to_string()); return LogosStatus::NullPointer; }}").unwrap();
1113                        emit_null_out_check(&mut out, "LogosStatus::NullPointer");
1114                        writeln!(out, "        let json_str = unsafe {{ std::ffi::CStr::from_ptr(json).to_string_lossy() }};").unwrap();
1115                        writeln!(out, "        match serde_json::from_str::<{}>(&json_str) {{", rust_struct_name).unwrap();
1116                        writeln!(out, "            Ok(val) => {{").unwrap();
1117                        writeln!(out, "                let __ptr = Box::into_raw(Box::new(val)) as usize;").unwrap();
1118                        writeln!(out, "                let mut __reg = logos_handle_registry().lock().unwrap_or_else(|e| e.into_inner());").unwrap();
1119                        writeln!(out, "                let (__id, _) = __reg.register(__ptr);").unwrap();
1120                        writeln!(out, "                unsafe {{ *out = __id as LogosHandle; }}").unwrap();
1121                        writeln!(out, "                LogosStatus::Ok").unwrap();
1122                        writeln!(out, "            }}").unwrap();
1123                        writeln!(out, "            Err(e) => {{ logos_set_last_error(e.to_string()); LogosStatus::DeserializationFailed }}").unwrap();
1124                        writeln!(out, "        }}").unwrap();
1125                        emit_catch_unwind_close(&mut out, "LogosStatus::ThreadPanic");
1126                        writeln!(out, "}}\n").unwrap();
1127                    }
1128
1129                    // free
1130                    writeln!(out, "#[no_mangle]").unwrap();
1131                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled_struct).unwrap();
1132                    emit_catch_unwind_open(&mut out);
1133                    emit_registry_free(&mut out, &rust_struct_name);
1134                    emit_catch_unwind_close(&mut out, "()");
1135                    writeln!(out, "}}\n").unwrap();
1136                }
1137                Some(TypeDef::Enum { variants, .. }) => {
1138                    let mangled_enum = type_name.to_lowercase();
1139                    let rust_enum_name = type_name.to_string();
1140
1141                    // tag accessor
1142                    writeln!(out, "#[no_mangle]").unwrap();
1143                    writeln!(out, "pub extern \"C\" fn logos_{}_tag(handle: LogosHandle) -> i32 {{", mangled_enum).unwrap();
1144                    emit_catch_unwind_open(&mut out);
1145                    emit_null_handle_check(&mut out, "-1");
1146                    emit_registry_deref(&mut out, "-1");
1147                    writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_enum_name).unwrap();
1148                    writeln!(out, "        match obj {{").unwrap();
1149                    for (i, variant) in variants.iter().enumerate() {
1150                        let vname = interner.resolve(variant.name);
1151                        if variant.fields.is_empty() {
1152                            writeln!(out, "            {}::{} => {},", rust_enum_name, vname, i).unwrap();
1153                        } else {
1154                            writeln!(out, "            {}::{}{{ .. }} => {},", rust_enum_name, vname, i).unwrap();
1155                        }
1156                    }
1157                    writeln!(out, "        }}").unwrap();
1158                    emit_catch_unwind_close(&mut out, "-1");
1159                    writeln!(out, "}}\n").unwrap();
1160
1161                    for variant in variants {
1162                        let vname = interner.resolve(variant.name);
1163                        let vname_lower = vname.to_lowercase();
1164                        for field in &variant.fields {
1165                            let fname = interner.resolve(field.name);
1166                            let is_field_text = match &field.ty {
1167                                FieldType::Primitive(s) | FieldType::Named(s) => {
1168                                    let n = interner.resolve(*s);
1169                                    n == "Text" || n == "String"
1170                                }
1171                                _ => false,
1172                            };
1173
1174                            writeln!(out, "#[no_mangle]").unwrap();
1175                            if is_field_text {
1176                                writeln!(out, "pub extern \"C\" fn logos_{}_{}_{fname}(handle: LogosHandle) -> *mut std::os::raw::c_char {{",
1177                                    mangled_enum, vname_lower, fname = fname).unwrap();
1178                                emit_catch_unwind_open(&mut out);
1179                                emit_null_handle_check(&mut out, "std::ptr::null_mut()");
1180                                emit_registry_deref(&mut out, "std::ptr::null_mut()");
1181                                writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_enum_name).unwrap();
1182                                writeln!(out, "        if let {}::{} {{ {fname}, .. }} = obj {{", rust_enum_name, vname, fname = fname).unwrap();
1183                                writeln!(out, "            match std::ffi::CString::new({fname}.clone()) {{", fname = fname).unwrap();
1184                                writeln!(out, "                Ok(cstr) => cstr.into_raw(),").unwrap();
1185                                writeln!(out, "                Err(_) => {{ logos_set_last_error(\"Field contains null byte\".to_string()); std::ptr::null_mut() }}").unwrap();
1186                                writeln!(out, "            }}").unwrap();
1187                                writeln!(out, "        }} else {{ logos_set_last_error(\"Wrong variant: expected {}\".to_string()); std::ptr::null_mut() }}", vname).unwrap();
1188                                emit_catch_unwind_close(&mut out, "std::ptr::null_mut()");
1189                                writeln!(out, "}}\n").unwrap();
1190                            } else {
1191                                let field_rust_type = match &field.ty {
1192                                    FieldType::Primitive(s) | FieldType::Named(s) => {
1193                                        let n = interner.resolve(*s);
1194                                        match n {
1195                                            "Int" => "i64",
1196                                            "Nat" => "u64",
1197                                            "Real" | "Float" => "f64",
1198                                            "Bool" | "Boolean" => "bool",
1199                                            "Byte" => "u8",
1200                                            "Char" => "u32",
1201                                            _ => n,
1202                                        }
1203                                    }
1204                                    _ => "LogosHandle",
1205                                };
1206                                writeln!(out, "pub extern \"C\" fn logos_{}_{}_{fname}(handle: LogosHandle) -> {} {{",
1207                                    mangled_enum, vname_lower, field_rust_type, fname = fname).unwrap();
1208                                emit_catch_unwind_open(&mut out);
1209                                emit_null_handle_check(&mut out, "Default::default()");
1210                                emit_registry_deref(&mut out, "Default::default()");
1211                                writeln!(out, "        let obj = unsafe {{ &*(__ptr as *const {}) }};", rust_enum_name).unwrap();
1212                                writeln!(out, "        if let {}::{} {{ {fname}, .. }} = obj {{", rust_enum_name, vname, fname = fname).unwrap();
1213                                writeln!(out, "            {fname}.clone()", fname = fname).unwrap();
1214                                writeln!(out, "        }} else {{ logos_set_last_error(\"Wrong variant: expected {}\".to_string()); Default::default() }}", vname).unwrap();
1215                                emit_catch_unwind_close(&mut out, "Default::default()");
1216                                writeln!(out, "}}\n").unwrap();
1217                            }
1218                        }
1219                    }
1220
1221                    // free
1222                    writeln!(out, "#[no_mangle]").unwrap();
1223                    writeln!(out, "pub extern \"C\" fn logos_{}_free(handle: LogosHandle) {{", mangled_enum).unwrap();
1224                    emit_catch_unwind_open(&mut out);
1225                    emit_registry_free(&mut out, &rust_enum_name);
1226                    emit_catch_unwind_close(&mut out, "()");
1227                    writeln!(out, "}}\n").unwrap();
1228                }
1229                _ => {}
1230            }
1231        }
1232        _ => {}
1233    }
1234
1235    out
1236}
1237
1238/// Collect all unique reference types that appear in C-exported function signatures.
1239/// Used to emit accessor functions once per type.
1240pub(crate) fn collect_c_export_reference_types<'a>(
1241    stmts: &'a [Stmt<'a>],
1242    interner: &Interner,
1243    registry: &TypeRegistry,
1244) -> Vec<&'a TypeExpr<'a>> {
1245    let mut seen = HashSet::new();
1246    let mut types = Vec::new();
1247
1248    for stmt in stmts {
1249        if let Stmt::FunctionDef { is_exported: true, export_target, params, return_type, .. } = stmt {
1250            let is_c = match export_target {
1251                None => true,
1252                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1253            };
1254            if !is_c { continue; }
1255
1256            for (_, ty) in params.iter() {
1257                if classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ReferenceType {
1258                    let mangled = mangle_type_for_c(ty, interner);
1259                    if seen.insert(mangled) {
1260                        types.push(*ty);
1261                    }
1262                }
1263            }
1264            if let Some(ty) = return_type {
1265                if classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ReferenceType {
1266                    let mangled = mangle_type_for_c(ty, interner);
1267                    if seen.insert(mangled) {
1268                        types.push(*ty);
1269                    }
1270                }
1271            }
1272        }
1273    }
1274
1275    types
1276}
1277
1278/// Collect all user-defined struct Symbols that are used as C ABI value types in exports.
1279/// These structs need `#[repr(C)]` for stable field layout.
1280pub(crate) fn collect_c_export_value_type_structs(
1281    stmts: &[Stmt],
1282    interner: &Interner,
1283    registry: &TypeRegistry,
1284) -> HashSet<Symbol> {
1285    let mut value_structs = HashSet::new();
1286
1287    for stmt in stmts {
1288        if let Stmt::FunctionDef { is_exported: true, export_target, params, return_type, .. } = stmt {
1289            let is_c = match export_target {
1290                None => true,
1291                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1292            };
1293            if !is_c { continue; }
1294
1295            let all_types: Vec<&TypeExpr> = params.iter()
1296                .map(|(_, ty)| *ty)
1297                .chain(return_type.iter().copied())
1298                .collect();
1299
1300            for ty in all_types {
1301                if let TypeExpr::Primitive(sym) | TypeExpr::Named(sym) = ty {
1302                    if classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ValueType {
1303                        if registry.get(*sym).is_some() {
1304                            value_structs.insert(*sym);
1305                        }
1306                    }
1307                }
1308            }
1309        }
1310    }
1311
1312    value_structs
1313}
1314
1315/// Collect all user-defined struct Symbols that are used as C ABI reference types in exports.
1316/// These structs need serde derives for from_json/to_json support.
1317pub(crate) fn collect_c_export_ref_structs(
1318    stmts: &[Stmt],
1319    interner: &Interner,
1320    registry: &TypeRegistry,
1321) -> HashSet<Symbol> {
1322    let mut ref_structs = HashSet::new();
1323    let ref_types = collect_c_export_reference_types(stmts, interner, registry);
1324    for ty in ref_types {
1325        if let TypeExpr::Primitive(sym) | TypeExpr::Named(sym) = ty {
1326            if registry.get(*sym).map_or(false, |d| matches!(d, TypeDef::Struct { .. })) {
1327                ref_structs.insert(*sym);
1328            }
1329        }
1330    }
1331    ref_structs
1332}
1333
1334/// Generate the C header (.h) content for all C-exported functions.
1335///
1336/// Includes:
1337/// - Runtime types (logos_status_t, logos_handle_t)
1338/// - Runtime functions (logos_get_last_error, logos_clear_error, logos_free_string)
1339/// - Value-type struct definitions
1340/// - Exported function declarations
1341/// - Accessor function declarations for reference types
1342pub fn generate_c_header(
1343    stmts: &[Stmt],
1344    module_name: &str,
1345    interner: &Interner,
1346    registry: &TypeRegistry,
1347) -> String {
1348    let mut out = String::new();
1349    let guard = module_name.to_uppercase().replace('-', "_");
1350
1351    writeln!(out, "// Generated from {}.lg — LogicAffeine Universal ABI", module_name).unwrap();
1352    writeln!(out, "#ifndef {}_H", guard).unwrap();
1353    writeln!(out, "#define {}_H\n", guard).unwrap();
1354    writeln!(out, "#include <stdint.h>").unwrap();
1355    writeln!(out, "#include <stdbool.h>").unwrap();
1356    writeln!(out, "#include <stddef.h>\n").unwrap();
1357
1358    writeln!(out, "#ifdef __cplusplus").unwrap();
1359    writeln!(out, "extern \"C\" {{").unwrap();
1360    writeln!(out, "#endif\n").unwrap();
1361
1362    // Runtime types
1363    writeln!(out, "// ═══ Runtime ═══").unwrap();
1364    writeln!(out, "typedef enum {{").unwrap();
1365    writeln!(out, "    LOGOS_STATUS_OK = 0,").unwrap();
1366    writeln!(out, "    LOGOS_STATUS_ERROR = 1,").unwrap();
1367    writeln!(out, "    LOGOS_STATUS_REFINEMENT_VIOLATION = 2,").unwrap();
1368    writeln!(out, "    LOGOS_STATUS_NULL_POINTER = 3,").unwrap();
1369    writeln!(out, "    LOGOS_STATUS_OUT_OF_BOUNDS = 4,").unwrap();
1370    writeln!(out, "    LOGOS_STATUS_DESERIALIZATION_FAILED = 5,").unwrap();
1371    writeln!(out, "    LOGOS_STATUS_INVALID_HANDLE = 6,").unwrap();
1372    writeln!(out, "    LOGOS_STATUS_CONTAINS_NULL_BYTE = 7,").unwrap();
1373    writeln!(out, "    LOGOS_STATUS_THREAD_PANIC = 8,").unwrap();
1374    writeln!(out, "    LOGOS_STATUS_MEMORY_EXHAUSTED = 9,").unwrap();
1375    writeln!(out, "}} logos_status_t;\n").unwrap();
1376    writeln!(out, "typedef void* logos_handle_t;\n").unwrap();
1377    writeln!(out, "const char* logos_last_error(void);").unwrap();
1378    writeln!(out, "const char* logos_get_last_error(void);").unwrap();
1379    writeln!(out, "void logos_clear_error(void);").unwrap();
1380    writeln!(out, "void logos_free_string(char* str);\n").unwrap();
1381
1382    writeln!(out, "#define LOGOS_ABI_VERSION 1").unwrap();
1383    writeln!(out, "const char* logos_version(void);").unwrap();
1384    writeln!(out, "uint32_t logos_abi_version(void);\n").unwrap();
1385
1386    // Collect value-type user structs used in exports
1387    let mut emitted_structs = HashSet::new();
1388    for stmt in stmts {
1389        if let Stmt::FunctionDef { is_exported: true, export_target, params, return_type, .. } = stmt {
1390            let is_c = match export_target {
1391                None => true,
1392                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1393            };
1394            if !is_c { continue; }
1395
1396            // Check params and return for user struct types
1397            let all_types: Vec<&TypeExpr> = params.iter()
1398                .map(|(_, ty)| *ty)
1399                .chain(return_type.iter().copied())
1400                .collect();
1401
1402            for ty in all_types {
1403                if let TypeExpr::Primitive(sym) | TypeExpr::Named(sym) = ty {
1404                    if classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ValueType {
1405                        if let Some(TypeDef::Struct { fields, .. }) = registry.get(*sym) {
1406                            let name = interner.resolve(*sym);
1407                            if emitted_structs.insert(name.to_string()) {
1408                                writeln!(out, "// ═══ Value Types ═══").unwrap();
1409                                writeln!(out, "typedef struct {{").unwrap();
1410                                for field in fields {
1411                                    let c_type = map_field_type_to_c(&field.ty, interner);
1412                                    writeln!(out, "    {} {};", c_type, interner.resolve(field.name)).unwrap();
1413                                }
1414                                writeln!(out, "}} {};\n", name).unwrap();
1415                            }
1416                        }
1417                    }
1418                }
1419            }
1420        }
1421    }
1422
1423    // Exported function declarations
1424    writeln!(out, "// ═══ Exported Functions ═══").unwrap();
1425    for stmt in stmts {
1426        if let Stmt::FunctionDef { name, is_exported: true, export_target, params, return_type, .. } = stmt {
1427            let is_c = match export_target {
1428                None => true,
1429                Some(t) => interner.resolve(*t).eq_ignore_ascii_case("c"),
1430            };
1431            if !is_c { continue; }
1432
1433            let func_name = format!("logos_{}", interner.resolve(*name));
1434            let has_ref_return = return_type.map_or(false, |ty| {
1435                classify_type_for_c_abi(ty, interner, registry) == CAbiClass::ReferenceType
1436            });
1437            let has_text_return = return_type.map_or(false, |ty| is_text_type(ty, interner));
1438            let has_result_return = return_type.map_or(false, |ty| is_result_type(ty, interner));
1439            let has_refinement_param = params.iter().any(|(_, ty)| matches!(ty, TypeExpr::Refinement { .. }));
1440
1441            // Status-code pattern matches codegen: ref/text/result returns or refinement params
1442            let uses_status_code = has_ref_return || has_result_return || has_text_return || has_refinement_param;
1443
1444            // Build C parameter list (ref-type params always become logos_handle_t)
1445            let mut c_params = Vec::new();
1446            for (pname, ptype) in params.iter() {
1447                let pn = interner.resolve(*pname);
1448                if classify_type_for_c_abi(ptype, interner, registry) == CAbiClass::ReferenceType {
1449                    c_params.push(format!("logos_handle_t {}", pn));
1450                } else {
1451                    c_params.push(format!("{} {}", map_type_to_c_header(ptype, interner, false), pn));
1452                }
1453            }
1454
1455            if uses_status_code {
1456                // Out parameter for return value
1457                if let Some(ret_ty) = return_type {
1458                    if is_result_type(ret_ty, interner) {
1459                        if let TypeExpr::Generic { params: ref rparams, .. } = ret_ty {
1460                            if !rparams.is_empty() {
1461                                let ok_ty = &rparams[0];
1462                                if classify_type_for_c_abi(ok_ty, interner, registry) == CAbiClass::ReferenceType {
1463                                    c_params.push("logos_handle_t* out".to_string());
1464                                } else {
1465                                    c_params.push(format!("{}* out", map_type_to_c_header(ok_ty, interner, false)));
1466                                }
1467                            }
1468                        }
1469                    } else if classify_type_for_c_abi(ret_ty, interner, registry) == CAbiClass::ReferenceType {
1470                        c_params.push("logos_handle_t* out".to_string());
1471                    } else if has_text_return {
1472                        c_params.push("char** out".to_string());
1473                    }
1474                }
1475                writeln!(out, "logos_status_t {}({});", func_name, c_params.join(", ")).unwrap();
1476            } else {
1477                // Direct value return
1478                let ret = return_type
1479                    .map(|ty| map_type_to_c_header(ty, interner, true))
1480                    .unwrap_or_else(|| "void".to_string());
1481                writeln!(out, "{} {}({});", ret, func_name, c_params.join(", ")).unwrap();
1482            }
1483        }
1484    }
1485    writeln!(out).unwrap();
1486
1487    // Accessor function declarations for reference types
1488    let ref_types = collect_c_export_reference_types(stmts, interner, registry);
1489    if !ref_types.is_empty() {
1490        for ref_ty in &ref_types {
1491            let mangled = mangle_type_for_c(ref_ty, interner);
1492            writeln!(out, "// ═══ {} Accessors ═══", mangled).unwrap();
1493
1494            match ref_ty {
1495                TypeExpr::Generic { base, params } => {
1496                    let base_name = interner.resolve(*base);
1497                    match base_name {
1498                        "Seq" | "List" | "Vec" if !params.is_empty() => {
1499                            let is_inner_text = is_text_type(&params[0], interner);
1500                            writeln!(out, "size_t logos_{}_len(logos_handle_t handle);", mangled).unwrap();
1501                            if is_inner_text {
1502                                writeln!(out, "char* logos_{}_at(logos_handle_t handle, size_t index);", mangled).unwrap();
1503                            } else {
1504                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1505                                writeln!(out, "logos_status_t logos_{}_at(logos_handle_t handle, size_t index, {}* out);", mangled, inner_c).unwrap();
1506                            }
1507                            writeln!(out, "logos_handle_t logos_{}_create(void);", mangled).unwrap();
1508                            if is_inner_text {
1509                                writeln!(out, "void logos_{}_push(logos_handle_t handle, const char* value);", mangled).unwrap();
1510                            } else {
1511                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1512                                writeln!(out, "void logos_{}_push(logos_handle_t handle, {} value);", mangled, inner_c).unwrap();
1513                            }
1514                            if is_inner_text {
1515                                writeln!(out, "char* logos_{}_pop(logos_handle_t handle);", mangled).unwrap();
1516                            } else {
1517                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1518                                writeln!(out, "logos_status_t logos_{}_pop(logos_handle_t handle, {}* out);", mangled, inner_c).unwrap();
1519                            }
1520                            writeln!(out, "char* logos_{}_to_json(logos_handle_t handle);", mangled).unwrap();
1521                            writeln!(out, "logos_status_t logos_{}_from_json(const char* json, logos_handle_t* out);", mangled).unwrap();
1522                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", mangled).unwrap();
1523                        }
1524                        "Map" | "HashMap" if params.len() >= 2 => {
1525                            let is_key_text = is_text_type(&params[0], interner);
1526                            let is_val_text = is_text_type(&params[1], interner);
1527                            writeln!(out, "size_t logos_{}_len(logos_handle_t handle);", mangled).unwrap();
1528                            if is_key_text {
1529                                if is_val_text {
1530                                    writeln!(out, "char* logos_{}_get(logos_handle_t handle, const char* key);", mangled).unwrap();
1531                                } else {
1532                                    let val_c = map_type_to_c_header(&params[1], interner, false);
1533                                    writeln!(out, "logos_status_t logos_{}_get(logos_handle_t handle, const char* key, {}* out);", mangled, val_c).unwrap();
1534                                }
1535                            } else {
1536                                let key_c = map_type_to_c_header(&params[0], interner, false);
1537                                if is_val_text {
1538                                    writeln!(out, "char* logos_{}_get(logos_handle_t handle, {} key);", mangled, key_c).unwrap();
1539                                } else {
1540                                    let val_c = map_type_to_c_header(&params[1], interner, false);
1541                                    writeln!(out, "logos_status_t logos_{}_get(logos_handle_t handle, {} key, {}* out);", mangled, key_c, val_c).unwrap();
1542                                }
1543                            }
1544                            writeln!(out, "logos_handle_t logos_{}_keys(logos_handle_t handle);", mangled).unwrap();
1545                            writeln!(out, "logos_handle_t logos_{}_values(logos_handle_t handle);", mangled).unwrap();
1546                            writeln!(out, "logos_handle_t logos_{}_create(void);", mangled).unwrap();
1547                            if is_key_text {
1548                                let val_c = if is_val_text { "const char*".to_string() } else { map_type_to_c_header(&params[1], interner, false) };
1549                                writeln!(out, "void logos_{}_insert(logos_handle_t handle, const char* key, {} value);", mangled, val_c).unwrap();
1550                            } else {
1551                                let key_c = map_type_to_c_header(&params[0], interner, false);
1552                                let val_c = if is_val_text { "const char*".to_string() } else { map_type_to_c_header(&params[1], interner, false) };
1553                                writeln!(out, "void logos_{}_insert(logos_handle_t handle, {} key, {} value);", mangled, key_c, val_c).unwrap();
1554                            }
1555                            if is_key_text {
1556                                writeln!(out, "bool logos_{}_remove(logos_handle_t handle, const char* key);", mangled).unwrap();
1557                            } else {
1558                                let key_c = map_type_to_c_header(&params[0], interner, false);
1559                                writeln!(out, "bool logos_{}_remove(logos_handle_t handle, {} key);", mangled, key_c).unwrap();
1560                            }
1561                            writeln!(out, "char* logos_{}_to_json(logos_handle_t handle);", mangled).unwrap();
1562                            writeln!(out, "logos_status_t logos_{}_from_json(const char* json, logos_handle_t* out);", mangled).unwrap();
1563                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", mangled).unwrap();
1564                        }
1565                        "Set" | "HashSet" if !params.is_empty() => {
1566                            let is_inner_text = is_text_type(&params[0], interner);
1567                            writeln!(out, "size_t logos_{}_len(logos_handle_t handle);", mangled).unwrap();
1568                            if is_inner_text {
1569                                writeln!(out, "bool logos_{}_contains(logos_handle_t handle, const char* value);", mangled).unwrap();
1570                            } else {
1571                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1572                                writeln!(out, "bool logos_{}_contains(logos_handle_t handle, {} value);", mangled, inner_c).unwrap();
1573                            }
1574                            writeln!(out, "logos_handle_t logos_{}_create(void);", mangled).unwrap();
1575                            if is_inner_text {
1576                                writeln!(out, "void logos_{}_insert(logos_handle_t handle, const char* value);", mangled).unwrap();
1577                            } else {
1578                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1579                                writeln!(out, "void logos_{}_insert(logos_handle_t handle, {} value);", mangled, inner_c).unwrap();
1580                            }
1581                            if is_inner_text {
1582                                writeln!(out, "bool logos_{}_remove(logos_handle_t handle, const char* value);", mangled).unwrap();
1583                            } else {
1584                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1585                                writeln!(out, "bool logos_{}_remove(logos_handle_t handle, {} value);", mangled, inner_c).unwrap();
1586                            }
1587                            writeln!(out, "char* logos_{}_to_json(logos_handle_t handle);", mangled).unwrap();
1588                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", mangled).unwrap();
1589                        }
1590                        "Option" | "Maybe" if !params.is_empty() => {
1591                            let is_inner_text = is_text_type(&params[0], interner);
1592                            writeln!(out, "bool logos_{}_is_some(logos_handle_t handle);", mangled).unwrap();
1593                            if is_inner_text {
1594                                writeln!(out, "char* logos_{}_unwrap(logos_handle_t handle);", mangled).unwrap();
1595                            } else {
1596                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1597                                writeln!(out, "logos_status_t logos_{}_unwrap(logos_handle_t handle, {}* out);", mangled, inner_c).unwrap();
1598                            }
1599                            if is_inner_text {
1600                                writeln!(out, "logos_handle_t logos_{}_some(const char* value);", mangled).unwrap();
1601                            } else {
1602                                let inner_c = map_type_to_c_header(&params[0], interner, false);
1603                                writeln!(out, "logos_handle_t logos_{}_some({} value);", mangled, inner_c).unwrap();
1604                            }
1605                            writeln!(out, "logos_handle_t logos_{}_none(void);", mangled).unwrap();
1606                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", mangled).unwrap();
1607                        }
1608                        _ => {}
1609                    }
1610                }
1611                TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
1612                    let type_name = interner.resolve(*sym);
1613                    match registry.get(*sym) {
1614                        Some(TypeDef::Struct { fields, is_portable, .. }) => {
1615                            let struct_lower = type_name.to_lowercase();
1616                            for field in fields {
1617                                let field_name = interner.resolve(field.name);
1618                                let is_field_text = match &field.ty {
1619                                    FieldType::Primitive(s) | FieldType::Named(s) => {
1620                                        let n = interner.resolve(*s);
1621                                        n == "Text" || n == "String"
1622                                    }
1623                                    _ => false,
1624                                };
1625                                if is_field_text {
1626                                    writeln!(out, "char* logos_{}_{}(logos_handle_t handle);", struct_lower, field_name).unwrap();
1627                                } else {
1628                                    let c_type = map_field_type_to_c(&field.ty, interner);
1629                                    writeln!(out, "{} logos_{}_{}(logos_handle_t handle);", c_type, struct_lower, field_name).unwrap();
1630                                }
1631                            }
1632                            // to_json/from_json always available for C export reference-type structs
1633                            writeln!(out, "char* logos_{}_to_json(logos_handle_t handle);", struct_lower).unwrap();
1634                            writeln!(out, "logos_status_t logos_{}_from_json(const char* json, logos_handle_t* out);", struct_lower).unwrap();
1635                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", struct_lower).unwrap();
1636                        }
1637                        Some(TypeDef::Enum { variants, .. }) => {
1638                            let enum_lower = type_name.to_lowercase();
1639                            // Tag enum constants
1640                            writeln!(out, "typedef enum {{").unwrap();
1641                            for (i, variant) in variants.iter().enumerate() {
1642                                let vname = interner.resolve(variant.name).to_uppercase();
1643                                writeln!(out, "    LOGOS_{}_{} = {},", type_name.to_uppercase(), vname, i).unwrap();
1644                            }
1645                            writeln!(out, "}} logos_{}_tag_t;", enum_lower).unwrap();
1646                            writeln!(out, "int32_t logos_{}_tag(logos_handle_t handle);", enum_lower).unwrap();
1647                            // Per-variant field accessors
1648                            for variant in variants {
1649                                let vname = interner.resolve(variant.name);
1650                                let vname_lower = vname.to_lowercase();
1651                                for field in &variant.fields {
1652                                    let fname = interner.resolve(field.name);
1653                                    let is_field_text = match &field.ty {
1654                                        FieldType::Primitive(s) | FieldType::Named(s) => {
1655                                            let n = interner.resolve(*s);
1656                                            n == "Text" || n == "String"
1657                                        }
1658                                        _ => false,
1659                                    };
1660                                    if is_field_text {
1661                                        writeln!(out, "char* logos_{}_{}_{fname}(logos_handle_t handle);", enum_lower, vname_lower, fname = fname).unwrap();
1662                                    } else {
1663                                        let c_type = map_field_type_to_c(&field.ty, interner);
1664                                        writeln!(out, "{} logos_{}_{}_{fname}(logos_handle_t handle);", c_type, enum_lower, vname_lower, fname = fname).unwrap();
1665                                    }
1666                                }
1667                            }
1668                            writeln!(out, "void logos_{}_free(logos_handle_t handle);", enum_lower).unwrap();
1669                        }
1670                        _ => {}
1671                    }
1672                }
1673                _ => {}
1674            }
1675            writeln!(out).unwrap();
1676        }
1677    }
1678
1679    writeln!(out, "#ifdef __cplusplus").unwrap();
1680    writeln!(out, "}}").unwrap();
1681    writeln!(out, "#endif\n").unwrap();
1682    writeln!(out, "#endif // {}_H", guard).unwrap();
1683
1684    out
1685}
1686pub(crate) fn map_type_to_c_header(ty: &TypeExpr, interner: &Interner, is_return: bool) -> String {
1687    match ty {
1688        TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
1689            let name = interner.resolve(*sym);
1690            match name {
1691                "Int" => "int64_t".to_string(),
1692                "Nat" => "uint64_t".to_string(),
1693                "Real" | "Float" => "double".to_string(),
1694                "Bool" | "Boolean" => "bool".to_string(),
1695                "Byte" => "uint8_t".to_string(),
1696                "Char" => "uint32_t".to_string(), // UTF-32 char
1697                "Text" | "String" => {
1698                    if is_return { "char*".to_string() } else { "const char*".to_string() }
1699                }
1700                "Unit" => "void".to_string(),
1701                other => other.to_string(), // User struct name
1702            }
1703        }
1704        TypeExpr::Refinement { base, .. } => map_type_to_c_header(base, interner, is_return),
1705        TypeExpr::Generic { .. } => "logos_handle_t".to_string(),
1706        _ => "logos_handle_t".to_string(),
1707    }
1708}
1709
1710/// Map a FieldType (from TypeRegistry) to a C header type string.
1711pub(crate) fn map_field_type_to_c(ft: &FieldType, interner: &Interner) -> String {
1712    match ft {
1713        FieldType::Primitive(sym) | FieldType::Named(sym) => {
1714            let name = interner.resolve(*sym);
1715            match name {
1716                "Int" => "int64_t".to_string(),
1717                "Nat" => "uint64_t".to_string(),
1718                "Real" | "Float" => "double".to_string(),
1719                "Bool" | "Boolean" => "bool".to_string(),
1720                "Byte" => "uint8_t".to_string(),
1721                "Char" => "uint32_t".to_string(),
1722                "Text" | "String" => "const char*".to_string(),
1723                other => other.to_string(),
1724            }
1725        }
1726        FieldType::Generic { .. } => "logos_handle_t".to_string(),
1727        FieldType::TypeParam(_) => "logos_handle_t".to_string(),
1728    }
1729}