Skip to main content

decy_core/
lib.rs

1//! Core transpilation pipeline for C-to-Rust conversion.
2//!
3//! This crate orchestrates the entire transpilation process:
4//! 1. Parse C code (via decy-parser)
5//! 2. Convert to HIR (via decy-hir)
6//! 3. Analyze and infer types (via decy-analyzer)
7//! 4. Infer ownership and lifetimes (via decy-ownership)
8//! 5. Verify safety properties (via decy-verify)
9//! 6. Generate Rust code (via decy-codegen)
10
11#![warn(missing_docs)]
12#![warn(clippy::all)]
13#![deny(unsafe_code)]
14
15#[macro_use]
16#[allow(unused_macros)]
17mod generated_contracts;
18
19pub mod metrics;
20pub mod optimize;
21pub mod trace;
22
23pub use metrics::{
24    CompileMetrics, ConvergenceReport, EquivalenceMetrics, TierMetrics, TranspilationResult,
25};
26
27use anyhow::{Context, Result};
28use decy_analyzer::patterns::PatternDetector;
29use decy_codegen::CodeGenerator;
30use decy_hir::{HirExpression, HirFunction, HirStatement};
31use decy_ownership::{
32    array_slice::ArrayParameterTransformer, borrow_gen::BorrowGenerator,
33    classifier_integration::classify_with_rules, dataflow::DataflowAnalyzer,
34    lifetime::LifetimeAnalyzer, lifetime_gen::LifetimeAnnotator,
35};
36use decy_parser::parser::CParser;
37use decy_stdlib::StdlibPrototypes;
38use petgraph::graph::{DiGraph, NodeIndex};
39use petgraph::visit::Topo;
40use std::collections::HashMap;
41use std::path::{Path, PathBuf};
42
43/// Result of transpiling a single C file.
44///
45/// Contains the transpiled Rust code along with metadata about
46/// dependencies and exported symbols for cross-file reference tracking.
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct TranspiledFile {
49    /// Path to the original C source file
50    pub source_path: PathBuf,
51
52    /// Generated Rust code
53    pub rust_code: String,
54
55    /// List of C files this file depends on (#include dependencies)
56    pub dependencies: Vec<PathBuf>,
57
58    /// Functions exported by this file (for FFI and cross-file references)
59    pub functions_exported: Vec<String>,
60
61    /// FFI declarations (extern "C") for C↔Rust boundaries
62    pub ffi_declarations: String,
63}
64
65impl TranspiledFile {
66    /// Create a new TranspiledFile with the given data.
67    pub fn new(
68        source_path: PathBuf,
69        rust_code: String,
70        dependencies: Vec<PathBuf>,
71        functions_exported: Vec<String>,
72        ffi_declarations: String,
73    ) -> Self {
74        Self { source_path, rust_code, dependencies, functions_exported, ffi_declarations }
75    }
76}
77
78/// Context for tracking cross-file information during transpilation.
79///
80/// Maintains knowledge of types, functions, and other declarations
81/// across multiple C files to enable proper reference resolution.
82#[derive(Debug, Clone, Default)]
83pub struct ProjectContext {
84    /// Types (structs, enums) defined across the project
85    types: HashMap<String, String>,
86
87    /// Functions defined across the project
88    functions: HashMap<String, String>,
89
90    /// Transpiled files tracked in this context
91    transpiled_files: HashMap<PathBuf, TranspiledFile>,
92}
93
94impl ProjectContext {
95    /// Create a new empty project context.
96    pub fn new() -> Self {
97        Self::default()
98    }
99
100    /// Add a transpiled file to the context.
101    ///
102    /// This makes the file's types and functions available for
103    /// cross-file reference resolution.
104    pub fn add_transpiled_file(&mut self, file: &TranspiledFile) {
105        contract_pre_configuration!();
106        // Track file
107        self.transpiled_files.insert(file.source_path.clone(), file.clone());
108
109        // Extract types from rust_code (simplified: just track that types exist)
110        // In real implementation, would parse the Rust code
111        if file.rust_code.contains("struct") {
112            // Extract struct names (simplified pattern matching)
113            for line in file.rust_code.lines() {
114                if line.contains("struct") {
115                    if let Some(name) = self.extract_type_name(line) {
116                        self.types.insert(name.clone(), line.to_string());
117                    }
118                }
119            }
120        }
121
122        // Track exported functions
123        for func_name in &file.functions_exported {
124            self.functions
125                .insert(func_name.clone(), file.source_path.to_string_lossy().to_string());
126        }
127    }
128
129    /// Check if a type is defined in the project context.
130    pub fn has_type(&self, type_name: &str) -> bool {
131        self.types.contains_key(type_name)
132    }
133
134    /// Check if a function is defined in the project context.
135    pub fn has_function(&self, func_name: &str) -> bool {
136        self.functions.contains_key(func_name)
137    }
138
139    /// Get the source file that defines a given function.
140    pub fn get_function_source(&self, func_name: &str) -> Option<&str> {
141        self.functions.get(func_name).map(|s| s.as_str())
142    }
143
144    /// Helper: Extract type name from a line containing struct/enum definition
145    fn extract_type_name(&self, line: &str) -> Option<String> {
146        // Simplified: Extract "Point" from "pub struct Point {"
147        let words: Vec<&str> = line.split_whitespace().collect();
148        if let Some(idx) = words.iter().position(|&w| w == "struct" || w == "enum") {
149            if idx + 1 < words.len() {
150                let name = words[idx + 1].trim_end_matches('{').trim_end_matches('<');
151                return Some(name.to_string());
152            }
153        }
154        None
155    }
156}
157
158/// Dependency graph for tracking file dependencies and computing build order.
159///
160/// Uses a directed acyclic graph (DAG) to represent file dependencies,
161/// where an edge from A to B means "A depends on B" (A includes B).
162#[derive(Debug, Clone)]
163pub struct DependencyGraph {
164    /// Directed graph where nodes are file paths
165    graph: DiGraph<PathBuf, ()>,
166
167    /// Map from file path to node index for fast lookups
168    path_to_node: HashMap<PathBuf, NodeIndex>,
169}
170
171impl DependencyGraph {
172    /// Create a new empty dependency graph.
173    pub fn new() -> Self {
174        Self { graph: DiGraph::new(), path_to_node: HashMap::new() }
175    }
176
177    /// Check if the graph is empty (has no files).
178    pub fn is_empty(&self) -> bool {
179        self.graph.node_count() == 0
180    }
181
182    /// Get the number of files in the graph.
183    pub fn file_count(&self) -> usize {
184        self.graph.node_count()
185    }
186
187    /// Check if a file is in the graph.
188    pub fn contains_file(&self, path: &Path) -> bool {
189        self.path_to_node.contains_key(path)
190    }
191
192    /// Add a file to the graph.
193    ///
194    /// If the file already exists, this is a no-op.
195    pub fn add_file(&mut self, path: &Path) {
196        if !self.contains_file(path) {
197            let node = self.graph.add_node(path.to_path_buf());
198            self.path_to_node.insert(path.to_path_buf(), node);
199        }
200    }
201
202    /// Add a dependency relationship: `from` depends on `to`.
203    ///
204    /// Both files must already be added to the graph via `add_file`.
205    pub fn add_dependency(&mut self, from: &Path, to: &Path) {
206        let from_node =
207            *self.path_to_node.get(from).expect("from file must be added to graph first");
208        let to_node = *self.path_to_node.get(to).expect("to file must be added to graph first");
209
210        self.graph.add_edge(from_node, to_node, ());
211    }
212
213    /// Check if there is a direct dependency from `from` to `to`.
214    pub fn has_dependency(&self, from: &Path, to: &Path) -> bool {
215        if let (Some(&from_node), Some(&to_node)) =
216            (self.path_to_node.get(from), self.path_to_node.get(to))
217        {
218            self.graph.contains_edge(from_node, to_node)
219        } else {
220            false
221        }
222    }
223
224    /// Compute topological sort to determine build order.
225    ///
226    /// Returns files in the order they should be transpiled (dependencies first).
227    /// Returns an error if there are circular dependencies.
228    pub fn topological_sort(&self) -> Result<Vec<PathBuf>> {
229        // Check for cycles first
230        if petgraph::algo::is_cyclic_directed(&self.graph) {
231            return Err(anyhow::anyhow!("Circular dependency detected in file dependencies"));
232        }
233
234        let mut topo = Topo::new(&self.graph);
235        let mut build_order = Vec::new();
236
237        while let Some(node) = topo.next(&self.graph) {
238            if let Some(path) = self.graph.node_weight(node) {
239                build_order.push(path.clone());
240            }
241        }
242
243        // Reverse because we want dependencies before dependents
244        build_order.reverse();
245
246        Ok(build_order)
247    }
248
249    /// Build a dependency graph from a list of C files.
250    ///
251    /// Parses #include directives to build the dependency graph.
252    pub fn from_files(files: &[PathBuf]) -> Result<Self> {
253        let mut graph = Self::new();
254
255        // Add all files first
256        for file in files {
257            graph.add_file(file);
258        }
259
260        // Parse dependencies
261        for file in files {
262            let content = std::fs::read_to_string(file)
263                .with_context(|| format!("Failed to read file: {}", file.display()))?;
264
265            let includes = Self::parse_include_directives(&content);
266
267            // Resolve #include paths relative to the file's directory
268            let file_dir = file.parent().unwrap_or_else(|| Path::new("."));
269
270            for include in includes {
271                let include_path = file_dir.join(&include);
272
273                // Only add dependency if the included file is in our file list
274                if graph.contains_file(&include_path) {
275                    graph.add_dependency(file, &include_path);
276                }
277            }
278        }
279
280        Ok(graph)
281    }
282
283    /// Parse #include directives from C source code.
284    ///
285    /// Returns a list of filenames (e.g., ["utils.h", "stdio.h"]).
286    pub fn parse_include_directives(code: &str) -> Vec<String> {
287        contract_pre_parse!();
288        let mut includes = Vec::new();
289
290        for line in code.lines() {
291            let trimmed = line.trim();
292            if trimmed.starts_with("#include") {
293                // Extract filename from #include "file.h" or #include <file.h>
294                if let Some(start) = trimmed.find('"').or_else(|| trimmed.find('<')) {
295                    let end_char = if trimmed.chars().nth(start) == Some('"') { '"' } else { '>' };
296                    if let Some(end) = trimmed[start + 1..].find(end_char) {
297                        let filename = &trimmed[start + 1..start + 1 + end];
298                        includes.push(filename.to_string());
299                    }
300                }
301            }
302        }
303
304        includes
305    }
306
307    /// Check if a C header file has header guards (#ifndef/#define/#endif).
308    pub fn has_header_guard(path: &Path) -> Result<bool> {
309        let content = std::fs::read_to_string(path)
310            .with_context(|| format!("Failed to read file: {}", path.display()))?;
311
312        let has_ifndef = content.lines().any(|line| {
313            let trimmed = line.trim();
314            trimmed.starts_with("#ifndef") || trimmed.starts_with("#if !defined")
315        });
316
317        let has_define = content.lines().any(|line| line.trim().starts_with("#define"));
318        let has_endif = content.lines().any(|line| line.trim().starts_with("#endif"));
319
320        Ok(has_ifndef && has_define && has_endif)
321    }
322}
323
324impl Default for DependencyGraph {
325    fn default() -> Self {
326        Self::new()
327    }
328}
329
330/// Statistics for transpilation cache performance.
331#[derive(Debug, Clone)]
332pub struct CacheStatistics {
333    /// Number of cache hits
334    pub hits: usize,
335    /// Number of cache misses
336    pub misses: usize,
337    /// Total number of files in cache
338    pub total_files: usize,
339}
340
341/// Cache entry storing file hash and transpilation result.
342#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
343struct CacheEntry {
344    /// SHA-256 hash of the file content
345    hash: String,
346    /// Cached transpilation result
347    transpiled: TranspiledFile,
348    /// Hashes of dependencies (for invalidation)
349    dependency_hashes: HashMap<PathBuf, String>,
350}
351
352/// Transpilation cache for avoiding re-transpilation of unchanged files.
353///
354/// Uses SHA-256 hashing to detect file changes and supports disk persistence.
355/// Provides 10-20x speedup on cache hits.
356///
357/// # Examples
358///
359/// ```no_run
360/// use decy_core::{TranspilationCache, ProjectContext, transpile_file};
361/// use std::path::Path;
362///
363/// let mut cache = TranspilationCache::new();
364/// let path = Path::new("src/main.c");
365/// let context = ProjectContext::new();
366///
367/// // First transpilation - cache miss
368/// let result = transpile_file(path, &context)?;
369/// cache.insert(path, &result);
370///
371/// // Second access - cache hit (if file unchanged)
372/// if let Some(cached) = cache.get(path) {
373///     println!("Cache hit! Using cached result");
374/// }
375/// # Ok::<(), anyhow::Error>(())
376/// ```
377#[derive(Debug, Clone)]
378pub struct TranspilationCache {
379    /// Cache entries mapped by file path
380    entries: HashMap<PathBuf, CacheEntry>,
381    /// Cache directory for disk persistence
382    cache_dir: Option<PathBuf>,
383    /// Performance statistics
384    hits: usize,
385    misses: usize,
386}
387
388impl TranspilationCache {
389    /// Create a new empty transpilation cache.
390    pub fn new() -> Self {
391        Self { entries: HashMap::new(), cache_dir: None, hits: 0, misses: 0 }
392    }
393
394    /// Create a cache with a specific directory for persistence.
395    pub fn with_directory(cache_dir: &Path) -> Self {
396        Self {
397            entries: HashMap::new(),
398            cache_dir: Some(cache_dir.to_path_buf()),
399            hits: 0,
400            misses: 0,
401        }
402    }
403
404    /// Compute SHA-256 hash of a file's content.
405    ///
406    /// Returns a 64-character hex string.
407    pub fn compute_hash(&self, path: &Path) -> Result<String> {
408        use sha2::{Digest, Sha256};
409
410        let content = std::fs::read(path)
411            .with_context(|| format!("Failed to read file for hashing: {}", path.display()))?;
412
413        let mut hasher = Sha256::new();
414        hasher.update(&content);
415        let result = hasher.finalize();
416
417        Ok(format!("{:x}", result))
418    }
419
420    /// Insert a transpiled file into the cache.
421    pub fn insert(&mut self, path: &Path, transpiled: &TranspiledFile) {
422        contract_pre_configuration!();
423        let hash = match self.compute_hash(path) {
424            Ok(h) => h,
425            Err(_) => return, // Skip caching if hash fails
426        };
427
428        // Compute dependency hashes
429        let mut dependency_hashes = HashMap::new();
430        for dep_path in &transpiled.dependencies {
431            if let Ok(dep_hash) = self.compute_hash(dep_path) {
432                dependency_hashes.insert(dep_path.clone(), dep_hash);
433            }
434        }
435
436        let entry = CacheEntry { hash, transpiled: transpiled.clone(), dependency_hashes };
437
438        self.entries.insert(path.to_path_buf(), entry);
439    }
440
441    /// Get a cached transpilation result if the file hasn't changed.
442    ///
443    /// Returns `None` if:
444    /// - File is not in cache
445    /// - File content has changed
446    /// - Any dependency has changed
447    pub fn get(&mut self, path: &Path) -> Option<&TranspiledFile> {
448        let entry = self.entries.get(&path.to_path_buf())?;
449
450        // Check if file hash matches
451        let current_hash = self.compute_hash(path).ok()?;
452        if current_hash != entry.hash {
453            self.misses += 1;
454            return None;
455        }
456
457        // Check if any dependency has changed
458        for (dep_path, cached_hash) in &entry.dependency_hashes {
459            if let Ok(current_dep_hash) = self.compute_hash(dep_path) {
460                if &current_dep_hash != cached_hash {
461                    self.misses += 1;
462                    return None;
463                }
464            }
465        }
466
467        self.hits += 1;
468        Some(&entry.transpiled)
469    }
470
471    /// Save the cache to disk (if cache_dir is set).
472    pub fn save(&self) -> Result<()> {
473        let cache_dir =
474            self.cache_dir.as_ref().ok_or_else(|| anyhow::anyhow!("Cache directory not set"))?;
475
476        std::fs::create_dir_all(cache_dir).with_context(|| {
477            format!("Failed to create cache directory: {}", cache_dir.display())
478        })?;
479
480        let cache_file = cache_dir.join("cache.json");
481        let json =
482            serde_json::to_string_pretty(&self.entries).context("Failed to serialize cache")?;
483
484        std::fs::write(&cache_file, json)
485            .with_context(|| format!("Failed to write cache file: {}", cache_file.display()))?;
486
487        Ok(())
488    }
489
490    /// Load a cache from disk.
491    pub fn load(cache_dir: &Path) -> Result<Self> {
492        let cache_file = cache_dir.join("cache.json");
493
494        if !cache_file.exists() {
495            // No cache file exists yet, return empty cache
496            return Ok(Self::with_directory(cache_dir));
497        }
498
499        let json = std::fs::read_to_string(&cache_file)
500            .with_context(|| format!("Failed to read cache file: {}", cache_file.display()))?;
501
502        let entries: HashMap<PathBuf, CacheEntry> =
503            serde_json::from_str(&json).context("Failed to deserialize cache")?;
504
505        Ok(Self { entries, cache_dir: Some(cache_dir.to_path_buf()), hits: 0, misses: 0 })
506    }
507
508    /// Clear all cached entries.
509    pub fn clear(&mut self) {
510        self.entries.clear();
511        self.hits = 0;
512        self.misses = 0;
513    }
514
515    /// Get cache statistics.
516    pub fn statistics(&self) -> CacheStatistics {
517        CacheStatistics { hits: self.hits, misses: self.misses, total_files: self.entries.len() }
518    }
519}
520
521impl Default for TranspilationCache {
522    fn default() -> Self {
523        Self::new()
524    }
525}
526
527/// Preprocess #include directives in C source code (DECY-056).
528///
529/// Resolves and inlines #include directives recursively, tracking processed files
530/// to prevent infinite loops from circular dependencies.
531///
532/// **NEW (Stdlib Support)**: Injects built-in C stdlib prototypes when system
533/// headers are encountered, enabling parsing of code that uses stdlib functions
534/// without requiring actual header files.
535///
536/// # Arguments
537///
538/// * `source` - C source code with #include directives
539/// * `base_dir` - Base directory for resolving relative include paths (None = current dir)
540/// * `processed` - Set of already processed file paths (prevents circular includes)
541/// * `stdlib_prototypes` - Stdlib prototype database for injection (None = create new)
542/// * `injected_headers` - Set of already injected system headers (prevents duplicates)
543///
544/// # Returns
545///
546/// Preprocessed C code with includes inlined and stdlib prototypes injected
547fn preprocess_includes(
548    source: &str,
549    base_dir: Option<&Path>,
550    processed: &mut std::collections::HashSet<PathBuf>,
551    stdlib_prototypes: &StdlibPrototypes,
552    injected_headers: &mut std::collections::HashSet<String>,
553) -> Result<String> {
554    let mut result = String::new();
555    let base_dir = base_dir.unwrap_or_else(|| Path::new("."));
556
557    for line in source.lines() {
558        let trimmed = line.trim();
559
560        // Check for #include directive
561        if trimmed.starts_with("#include") {
562            // Extract filename from #include "file.h" or #include <file.h>
563            let (filename, is_system) = if let Some(start) = trimmed.find('"') {
564                if let Some(end) = trimmed[start + 1..].find('"') {
565                    let filename = &trimmed[start + 1..start + 1 + end];
566                    (filename, false)
567                } else {
568                    // Malformed include, keep original line
569                    result.push_str(line);
570                    result.push('\n');
571                    continue;
572                }
573            } else if let Some(start) = trimmed.find('<') {
574                if let Some(end) = trimmed[start + 1..].find('>') {
575                    let filename = &trimmed[start + 1..start + 1 + end];
576                    (filename, true)
577                } else {
578                    // Malformed include, keep original line
579                    result.push_str(line);
580                    result.push('\n');
581                    continue;
582                }
583            } else {
584                // No include found, keep original line
585                result.push_str(line);
586                result.push('\n');
587                continue;
588            };
589
590            // Skip system includes (<stdio.h>) - we don't have those files
591            // BUT: Inject stdlib prototypes so parsing succeeds
592            if is_system {
593                // Comment out the original include
594                result.push_str(&format!("// {}\n", line));
595
596                // Inject stdlib prototypes for this header (only once per header)
597                if !injected_headers.contains(filename) {
598                    // Mark as injected
599                    injected_headers.insert(filename.to_string());
600
601                    // Try to parse the header name and inject specific prototypes
602                    if let Some(header) = decy_stdlib::StdHeader::from_filename(filename) {
603                        result
604                            .push_str(&format!("// BEGIN: Built-in prototypes for {}\n", filename));
605                        result.push_str(&stdlib_prototypes.inject_prototypes_for_header(header));
606                        result.push_str(&format!("// END: Built-in prototypes for {}\n", filename));
607                    } else {
608                        // Unknown header - just comment it out
609                        result.push_str(&format!("// Unknown system header: {}\n", filename));
610                    }
611                }
612
613                continue;
614            }
615
616            // Resolve include path relative to base_dir
617            let include_path = base_dir.join(filename);
618
619            // Check if already processed (circular dependency or duplicate)
620            if processed.contains(&include_path) {
621                // Already processed, skip (header guards working)
622                result.push_str(&format!("// Already included: {}\n", filename));
623                continue;
624            }
625
626            // Try to read the included file
627            if let Ok(included_content) = std::fs::read_to_string(&include_path) {
628                // Mark as processed
629                processed.insert(include_path.clone());
630
631                // Get directory of included file for nested includes
632                let included_dir = include_path.parent().unwrap_or(base_dir);
633
634                // Recursively preprocess the included file
635                let preprocessed = preprocess_includes(
636                    &included_content,
637                    Some(included_dir),
638                    processed,
639                    stdlib_prototypes,
640                    injected_headers,
641                )?;
642
643                // Add marker comments for debugging
644                result.push_str(&format!("// BEGIN INCLUDE: {}\n", filename));
645                result.push_str(&preprocessed);
646                result.push_str(&format!("// END INCLUDE: {}\n", filename));
647            } else {
648                // File not found - return error for local includes
649                anyhow::bail!("Failed to find include file: {}", include_path.display());
650            }
651        } else {
652            // Regular line, keep as-is
653            result.push_str(line);
654            result.push('\n');
655        }
656    }
657
658    Ok(result)
659}
660
661/// Main transpilation pipeline entry point.
662///
663/// Converts C source code to safe Rust code with automatic ownership
664/// and lifetime inference.
665///
666/// Automatically preprocesses #include directives (DECY-056).
667///
668/// # Examples
669///
670/// ```no_run
671/// use decy_core::transpile;
672///
673/// let c_code = "int add(int a, int b) { return a + b; }";
674/// let rust_code = transpile(c_code)?;
675/// assert!(rust_code.contains("fn add"));
676/// # Ok::<(), anyhow::Error>(())
677/// ```
678///
679/// # Errors
680///
681/// Returns an error if:
682/// - #include file not found
683/// - C code parsing fails
684/// - HIR conversion fails
685/// - Code generation fails
686pub fn transpile(c_code: &str) -> Result<String> {
687    contract_pre_configuration!();
688    transpile_with_includes(c_code, None)
689}
690
691/// DECY-193: Transpile C code with decision tracing.
692///
693/// Returns both the transpiled Rust code and a trace of decisions made
694/// during transpilation (ownership inference, type mapping, etc.).
695///
696/// # Arguments
697///
698/// * `c_code` - C source code to transpile
699///
700/// # Returns
701///
702/// Returns a tuple of (rust_code, trace_collector).
703///
704/// # Examples
705///
706/// ```
707/// use decy_core::transpile_with_trace;
708///
709/// let c_code = "int add(int a, int b) { return a + b; }";
710/// let (code, trace) = transpile_with_trace(c_code)?;
711/// assert!(!code.is_empty());
712/// // Trace contains ownership inference decisions
713/// let json = trace.to_json();
714/// assert!(json.starts_with('['));
715/// # Ok::<(), anyhow::Error>(())
716/// ```
717pub fn transpile_with_trace(c_code: &str) -> Result<(String, trace::TraceCollector)> {
718    contract_pre_configuration!();
719    use trace::{DecisionType, PipelineStage, TraceCollector, TraceEntry};
720
721    let mut collector = TraceCollector::new();
722
723    // Record parsing start
724    collector.record(TraceEntry {
725        stage: PipelineStage::Parsing,
726        source_location: None,
727        decision_type: DecisionType::PatternDetection,
728        chosen: "clang-sys".to_string(),
729        alternatives: vec![],
730        confidence: 1.0,
731        reason: "Using clang-sys for C parsing".to_string(),
732    });
733
734    // Transpile normally
735    let rust_code = transpile(c_code)?;
736
737    // Record completion
738    collector.record(TraceEntry {
739        stage: PipelineStage::CodeGeneration,
740        source_location: None,
741        decision_type: DecisionType::PatternDetection,
742        chosen: "completed".to_string(),
743        alternatives: vec![],
744        confidence: 1.0,
745        reason: format!("Transpilation produced {} lines of Rust", rust_code.lines().count()),
746    });
747
748    Ok((rust_code, collector))
749}
750
751/// Transpile C code and return verification result.
752///
753/// This function transpiles C code to Rust and includes metadata about
754/// the transpilation for metrics tracking.
755///
756/// # Arguments
757///
758/// * `c_code` - C source code to transpile
759///
760/// # Returns
761///
762/// Returns a `TranspilationResult` containing the generated Rust code
763/// and verification status.
764///
765/// # Examples
766///
767/// ```
768/// use decy_core::transpile_with_verification;
769///
770/// let c_code = "int add(int a, int b) { return a + b; }";
771/// let result = transpile_with_verification(c_code);
772/// assert!(result.is_ok());
773/// ```
774pub fn transpile_with_verification(c_code: &str) -> Result<TranspilationResult> {
775    contract_pre_contract_composition!();
776    match transpile(c_code) {
777        Ok(rust_code) => Ok(TranspilationResult::success(rust_code)),
778        Err(e) => {
779            // Return empty code with error
780            Ok(TranspilationResult::failure(String::new(), vec![e.to_string()]))
781        }
782    }
783}
784
785fn deduplicate_functions(all_hir_functions: Vec<HirFunction>) -> Vec<HirFunction> {
786    let mut func_map: HashMap<String, HirFunction> = HashMap::new();
787
788    for func in all_hir_functions {
789        let name = func.name().to_string();
790        if let Some(existing) = func_map.get(&name) {
791            if func.has_body() && !existing.has_body() {
792                func_map.insert(name, func);
793            }
794        } else {
795            func_map.insert(name, func);
796        }
797    }
798
799    let mut funcs: Vec<_> = func_map.into_values().collect();
800    funcs.sort_by(|a, b| a.name().cmp(b.name()));
801    funcs
802}
803
804fn build_slice_func_arg_mappings(
805    hir_functions: &[HirFunction],
806) -> Vec<(String, Vec<(usize, usize)>)> {
807    hir_functions
808        .iter()
809        .filter_map(|func| {
810            let mut mappings = Vec::new();
811            let params = func.parameters();
812
813            for (i, param) in params.iter().enumerate() {
814                if matches!(param.param_type(), decy_hir::HirType::Pointer(_)) {
815                    if i + 1 < params.len() {
816                        let next_param = &params[i + 1];
817                        if matches!(next_param.param_type(), decy_hir::HirType::Int) {
818                            let param_name = next_param.name().to_lowercase();
819                            if param_name.contains("len")
820                                || param_name.contains("size")
821                                || param_name.contains("count")
822                                || param_name == "n"
823                                || param_name == "num"
824                            {
825                                mappings.push((i, i + 1));
826                            }
827                        }
828                    }
829                }
830            }
831
832            if mappings.is_empty() {
833                None
834            } else {
835                Some((func.name().to_string(), mappings))
836            }
837        })
838        .collect()
839}
840
841fn transform_function_with_ownership(
842    func: HirFunction,
843) -> (HirFunction, decy_ownership::lifetime_gen::AnnotatedSignature) {
844    let dataflow_analyzer = DataflowAnalyzer::new();
845    let dataflow_graph = dataflow_analyzer.analyze(&func);
846
847    let ownership_inferences = classify_with_rules(&dataflow_graph, &func);
848
849    let borrow_generator = BorrowGenerator::new();
850    let func_with_borrows = borrow_generator.transform_function(&func, &ownership_inferences);
851
852    let array_transformer = ArrayParameterTransformer::new();
853    let func_with_slices = array_transformer.transform(&func_with_borrows, &dataflow_graph);
854
855    let lifetime_analyzer = LifetimeAnalyzer::new();
856    let scope_tree = lifetime_analyzer.build_scope_tree(&func_with_slices);
857    let _lifetimes = lifetime_analyzer.track_lifetimes(&func_with_slices, &scope_tree);
858
859    let lifetime_annotator = LifetimeAnnotator::new();
860    let annotated_signature = lifetime_annotator.annotate_function(&func_with_slices);
861
862    let optimized_func = optimize::optimize_function(&func_with_slices);
863
864    (optimized_func, annotated_signature)
865}
866
867fn const_struct_literal(
868    struct_name: &str,
869    hir_structs: &[decy_hir::HirStruct],
870) -> String {
871    if let Some(hir_struct) = hir_structs.iter().find(|s| s.name() == struct_name) {
872        let field_inits: Vec<String> = hir_struct
873            .fields()
874            .iter()
875            .map(|f| {
876                let default_val = match f.field_type() {
877                    decy_hir::HirType::Int => "0".to_string(),
878                    decy_hir::HirType::UnsignedInt => "0".to_string(),
879                    decy_hir::HirType::Char => "0".to_string(),
880                    decy_hir::HirType::SignedChar => "0".to_string(),
881                    decy_hir::HirType::Float => "0.0".to_string(),
882                    decy_hir::HirType::Double => "0.0".to_string(),
883                    decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
884                    decy_hir::HirType::Array { size: Some(n), element_type } => {
885                        let elem = match element_type.as_ref() {
886                            decy_hir::HirType::Char => "0u8",
887                            decy_hir::HirType::SignedChar => "0i8",
888                            decy_hir::HirType::Int => "0i32",
889                            _ => "0",
890                        };
891                        format!("[{}; {}]", elem, n)
892                    }
893                    _ => "Default::default()".to_string(),
894                };
895                format!("{}: {}", f.name(), default_val)
896            })
897            .collect();
898        format!("{} {{ {} }}", struct_name, field_inits.join(", "))
899    } else {
900        format!("{}::default()", struct_name)
901    }
902}
903
904fn generate_initialized_global_code(
905    name: &str,
906    var_type: &decy_hir::HirType,
907    init_expr: &decy_hir::HirExpression,
908    type_str: &str,
909    hir_structs: &[decy_hir::HirStruct],
910    code_generator: &CodeGenerator,
911) -> String {
912    let init_code =
913        if let decy_hir::HirType::Array { element_type, size: Some(size_val) } = var_type {
914            if matches!(init_expr, decy_hir::HirExpression::IntLiteral(_)) {
915                let element_init = match element_type.as_ref() {
916                    decy_hir::HirType::Char => "0u8".to_string(),
917                    decy_hir::HirType::SignedChar => "0i8".to_string(),
918                    decy_hir::HirType::Int => "0i32".to_string(),
919                    decy_hir::HirType::UnsignedInt => "0u32".to_string(),
920                    decy_hir::HirType::Float => "0.0f32".to_string(),
921                    decy_hir::HirType::Double => "0.0f64".to_string(),
922                    decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
923                    decy_hir::HirType::Struct(sname) => const_struct_literal(sname, hir_structs),
924                    _ => "0".to_string(),
925                };
926                format!("[{}; {}]", element_init, size_val)
927            } else {
928                code_generator.generate_expression(init_expr)
929            }
930        } else {
931            code_generator.generate_expression(init_expr)
932        };
933    format!("static mut {}: {} = {};\n", name, type_str, init_code)
934}
935
936fn generate_uninitialized_global_code(
937    name: &str,
938    var_type: &decy_hir::HirType,
939    type_str: &str,
940    hir_structs: &[decy_hir::HirStruct],
941) -> Option<String> {
942    match var_type {
943        decy_hir::HirType::FunctionPointer { .. } => {
944            Some(format!("static mut {}: Option<{}> = None;\n", name, type_str))
945        }
946        _ => {
947            let default_value = match var_type {
948                decy_hir::HirType::Int => "0".to_string(),
949                decy_hir::HirType::UnsignedInt => "0".to_string(),
950                decy_hir::HirType::Char => "0".to_string(),
951                decy_hir::HirType::SignedChar => "0".to_string(),
952                decy_hir::HirType::Float => "0.0".to_string(),
953                decy_hir::HirType::Double => "0.0".to_string(),
954                decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
955                decy_hir::HirType::Array { element_type, size } => {
956                    let elem_default = match element_type.as_ref() {
957                        decy_hir::HirType::Char => "0u8".to_string(),
958                        decy_hir::HirType::SignedChar => "0i8".to_string(),
959                        decy_hir::HirType::Int => "0i32".to_string(),
960                        decy_hir::HirType::UnsignedInt => "0u32".to_string(),
961                        decy_hir::HirType::Float => "0.0f32".to_string(),
962                        decy_hir::HirType::Double => "0.0f64".to_string(),
963                        decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
964                        decy_hir::HirType::Struct(sname) => {
965                            const_struct_literal(sname, hir_structs)
966                        }
967                        _ => "0".to_string(),
968                    };
969                    if let Some(n) = size {
970                        format!("[{}; {}]", elem_default, n)
971                    } else {
972                        format!("[{}; 0]", elem_default)
973                    }
974                }
975                _ => "Default::default()".to_string(),
976            };
977            Some(format!("static mut {}: {} = {};\n", name, type_str, default_value))
978        }
979    }
980}
981
982fn generate_global_variable_code(
983    hir_variables: &[decy_hir::HirStatement],
984    hir_structs: &[decy_hir::HirStruct],
985    code_generator: &CodeGenerator,
986    rust_code: &mut String,
987) -> Vec<(String, decy_hir::HirType)> {
988    let mut global_vars: Vec<(String, decy_hir::HirType)> = Vec::new();
989    for var_stmt in hir_variables {
990        if let decy_hir::HirStatement::VariableDeclaration { name, var_type, initializer } =
991            var_stmt
992        {
993            global_vars.push((name.clone(), var_type.clone()));
994            let type_str = CodeGenerator::map_type(var_type);
995
996            if let Some(init_expr) = initializer {
997                rust_code.push_str(&generate_initialized_global_code(
998                    name,
999                    var_type,
1000                    init_expr,
1001                    &type_str,
1002                    hir_structs,
1003                    code_generator,
1004                ));
1005            } else if let Some(code) =
1006                generate_uninitialized_global_code(name, var_type, &type_str, hir_structs)
1007            {
1008                rust_code.push_str(&code);
1009            }
1010        }
1011    }
1012    if !hir_variables.is_empty() {
1013        rust_code.push('\n');
1014    }
1015    global_vars
1016}
1017
1018fn build_all_function_sigs(
1019    transformed_functions: &[(HirFunction, decy_ownership::lifetime_gen::AnnotatedSignature)],
1020) -> Vec<(String, Vec<decy_hir::HirType>)> {
1021    transformed_functions
1022        .iter()
1023        .map(|(func, _sig)| {
1024            let param_types: Vec<decy_hir::HirType> = func
1025                .parameters()
1026                .iter()
1027                .map(|p| {
1028                    if let decy_hir::HirType::Pointer(inner) = p.param_type() {
1029                        if uses_pointer_arithmetic(func, p.name())
1030                            || pointer_compared_to_null(func, p.name())
1031                        {
1032                            p.param_type().clone()
1033                        } else {
1034                            decy_hir::HirType::Reference { inner: inner.clone(), mutable: true }
1035                        }
1036                    } else {
1037                        p.param_type().clone()
1038                    }
1039                })
1040                .collect();
1041            (func.name().to_string(), param_types)
1042        })
1043        .collect()
1044}
1045
1046/// Transpile C code with include directive support and custom base directory.
1047///
1048/// # Arguments
1049///
1050/// * `c_code` - C source code to transpile
1051/// * `base_dir` - Base directory for resolving #include paths (None = current dir)
1052///
1053/// # Examples
1054///
1055/// ```no_run
1056/// use decy_core::transpile_with_includes;
1057/// use std::path::Path;
1058///
1059/// let c_code = "#include \"utils.h\"\nint main() { return 0; }";
1060/// let rust_code = transpile_with_includes(c_code, Some(Path::new("/tmp/project")))?;
1061/// # Ok::<(), anyhow::Error>(())
1062/// ```
1063pub fn transpile_with_includes(c_code: &str, base_dir: Option<&Path>) -> Result<String> {
1064    contract_pre_configuration!();
1065    // Step 0: Preprocess #include directives (DECY-056) + Inject stdlib prototypes
1066    let stdlib_prototypes = StdlibPrototypes::new();
1067    let mut processed_files = std::collections::HashSet::new();
1068    let mut injected_headers = std::collections::HashSet::new();
1069    let preprocessed = preprocess_includes(
1070        c_code,
1071        base_dir,
1072        &mut processed_files,
1073        &stdlib_prototypes,
1074        &mut injected_headers,
1075    )?;
1076
1077    // Step 1: Parse C code
1078    // Note: We don't add standard type definitions (size_t, etc.) here because:
1079    // 1. If code has #include directives, system headers define them
1080    // 2. If code doesn't have includes and uses size_t, it should typedef it explicitly
1081    // 3. Adding conflicting typedefs breaks parsing
1082    let parser = CParser::new().context("Failed to create C parser")?;
1083    let ast = parser.parse(&preprocessed).context("Failed to parse C code")?;
1084
1085    // Step 2: Convert to HIR
1086    let all_hir_functions: Vec<HirFunction> =
1087        ast.functions().iter().map(HirFunction::from_ast_function).collect();
1088
1089    // DECY-190: Deduplicate functions - when a C file has both a declaration
1090    // (prototype) and a definition, only keep the definition.
1091    // This prevents "the name X is defined multiple times" errors in Rust.
1092    let hir_functions = deduplicate_functions(all_hir_functions);
1093
1094    // Convert structs to HIR
1095    let hir_structs: Vec<decy_hir::HirStruct> = ast
1096        .structs()
1097        .iter()
1098        .map(|s| {
1099            let fields = s
1100                .fields
1101                .iter()
1102                .map(|f| {
1103                    decy_hir::HirStructField::new(
1104                        f.name.clone(),
1105                        decy_hir::HirType::from_ast_type(&f.field_type),
1106                    )
1107                })
1108                .collect();
1109            decy_hir::HirStruct::new(s.name.clone(), fields)
1110        })
1111        .collect();
1112
1113    // DECY-240: Convert enums to HIR
1114    let hir_enums: Vec<decy_hir::HirEnum> = ast
1115        .enums()
1116        .iter()
1117        .map(|e| {
1118            let variants = e
1119                .variants
1120                .iter()
1121                .map(|v| {
1122                    decy_hir::HirEnumVariant::new(v.name.clone(), v.value.map(|val| val as i32))
1123                })
1124                .collect();
1125            decy_hir::HirEnum::new(e.name.clone(), variants)
1126        })
1127        .collect();
1128
1129    // Convert global variables to HIR (DECY-054)
1130    // DECY-223: Filter out extern references (they refer to existing globals, not new definitions)
1131    // Also deduplicate by name (first definition wins)
1132    let mut seen_globals: std::collections::HashSet<String> = std::collections::HashSet::new();
1133    let hir_variables: Vec<decy_hir::HirStatement> = ast
1134        .variables()
1135        .iter()
1136        .filter(|v| {
1137            // Skip extern declarations without initializers (they're references, not definitions)
1138            // extern int max; → skip (reference)
1139            // int max = 0; → keep (definition)
1140            // extern int max = 0; → keep (definition with extern linkage)
1141            if v.is_extern() && v.initializer().is_none() {
1142                return false;
1143            }
1144            // Deduplicate by name
1145            if seen_globals.contains(v.name()) {
1146                return false;
1147            }
1148            seen_globals.insert(v.name().to_string());
1149            true
1150        })
1151        .map(|v| decy_hir::HirStatement::VariableDeclaration {
1152            name: v.name().to_string(),
1153            var_type: decy_hir::HirType::from_ast_type(v.var_type()),
1154            initializer: v.initializer().map(decy_hir::HirExpression::from_ast_expression),
1155        })
1156        .collect();
1157
1158    // Convert typedefs to HIR (DECY-054, DECY-057)
1159    let hir_typedefs: Vec<decy_hir::HirTypedef> = ast
1160        .typedefs()
1161        .iter()
1162        .map(|t| {
1163            decy_hir::HirTypedef::new(
1164                t.name().to_string(),
1165                decy_hir::HirType::from_ast_type(&t.underlying_type),
1166            )
1167        })
1168        .collect();
1169
1170    // DECY-116: Build slice function arg mappings BEFORE transformation (while we still have original params)
1171    let slice_func_args = build_slice_func_arg_mappings(&hir_functions);
1172
1173    // Step 3: Analyze ownership and lifetimes
1174    let transformed_functions: Vec<_> = hir_functions
1175        .into_iter()
1176        .map(|func| transform_function_with_ownership(func))
1177        .collect();
1178
1179    // Step 4: Generate Rust code with lifetime annotations
1180    let code_generator = CodeGenerator::new();
1181    let mut rust_code = String::new();
1182
1183    // DECY-119: Track emitted definitions to avoid duplicates
1184    let mut emitted_structs = std::collections::HashSet::new();
1185    let mut emitted_typedefs = std::collections::HashSet::new();
1186
1187    // Generate struct definitions first (deduplicated)
1188    for hir_struct in &hir_structs {
1189        let struct_name = hir_struct.name();
1190        if emitted_structs.contains(struct_name) {
1191            continue; // Skip duplicate
1192        }
1193        emitted_structs.insert(struct_name.to_string());
1194
1195        let struct_code = code_generator.generate_struct(hir_struct);
1196        rust_code.push_str(&struct_code);
1197        rust_code.push('\n');
1198    }
1199
1200    // DECY-240: Generate enum definitions (as const i32 values)
1201    for hir_enum in &hir_enums {
1202        let enum_code = code_generator.generate_enum(hir_enum);
1203        rust_code.push_str(&enum_code);
1204        rust_code.push('\n');
1205    }
1206
1207    // DECY-204: Convert C++ classes to HIR and generate struct + impl + Drop
1208    let hir_classes: Vec<decy_hir::HirClass> = ast
1209        .classes()
1210        .iter()
1211        .map(decy_hir::HirClass::from_ast_class)
1212        .collect();
1213
1214    for hir_class in &hir_classes {
1215        let class_code = code_generator.generate_class(hir_class);
1216        rust_code.push_str(&class_code);
1217        rust_code.push('\n');
1218    }
1219
1220    // DECY-204: Convert C++ namespaces to HIR and generate mod blocks
1221    let hir_namespaces: Vec<decy_hir::HirNamespace> = ast
1222        .namespaces()
1223        .iter()
1224        .map(decy_hir::HirNamespace::from_ast_namespace)
1225        .collect();
1226
1227    for hir_ns in &hir_namespaces {
1228        let ns_code = code_generator.generate_namespace(hir_ns);
1229        rust_code.push_str(&ns_code);
1230        rust_code.push('\n');
1231    }
1232
1233    // DECY-241: Add errno global variable (C compatibility)
1234    rust_code.push_str("static mut ERRNO: i32 = 0;\n");
1235
1236    // Generate typedefs (DECY-054, DECY-057) - deduplicated
1237    for typedef in &hir_typedefs {
1238        let typedef_name = typedef.name();
1239        if emitted_typedefs.contains(typedef_name) {
1240            continue; // Skip duplicate
1241        }
1242        emitted_typedefs.insert(typedef_name.to_string());
1243
1244        if let Ok(typedef_code) = code_generator.generate_typedef(typedef) {
1245            rust_code.push_str(&typedef_code);
1246            rust_code.push('\n');
1247        }
1248    }
1249
1250    // Generate global variables and collect their names/types
1251    let global_vars = generate_global_variable_code(
1252        &hir_variables,
1253        &hir_structs,
1254        &code_generator,
1255        &mut rust_code,
1256    );
1257
1258    // DECY-117: Build function signatures for call site reference mutability
1259    let all_function_sigs = build_all_function_sigs(&transformed_functions);
1260
1261    // DECY-134b: Build string iteration function info for call site transformation
1262    let string_iter_funcs: Vec<(String, Vec<(usize, bool)>)> = transformed_functions
1263        .iter()
1264        .filter_map(|(func, _)| {
1265            let params = code_generator.get_string_iteration_params(func);
1266            if params.is_empty() {
1267                None
1268            } else {
1269                Some((func.name().to_string(), params))
1270            }
1271        })
1272        .collect();
1273
1274    // Generate functions with struct definitions for field type awareness
1275    // Note: slice_func_args was built at line 814 BEFORE transformation to capture original params
1276    // DECY-220/233: Pass global_vars for unsafe access tracking and type inference
1277    for (func, annotated_sig) in &transformed_functions {
1278        let generated = code_generator.generate_function_with_lifetimes_and_structs(
1279            func,
1280            annotated_sig,
1281            &hir_structs,
1282            &all_function_sigs,
1283            &slice_func_args,
1284            &string_iter_funcs,
1285            &global_vars,
1286        );
1287        rust_code.push_str(&generated);
1288        rust_code.push('\n');
1289    }
1290
1291    Ok(rust_code)
1292}
1293
1294/// DECY-237: Transpile directly from a C file path.
1295/// This uses clang's native file parsing which properly resolves system headers.
1296///
1297/// # Arguments
1298///
1299/// * `file_path` - Path to the C source file
1300///
1301/// # Returns
1302///
1303/// Generated Rust code as a string.
1304pub fn transpile_from_file_path(file_path: &Path) -> Result<String> {
1305    contract_pre_configuration!();
1306    // Read the source code
1307    let c_code = std::fs::read_to_string(file_path)
1308        .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
1309
1310    // Use transpile_with_file which uses file-based parsing
1311    transpile_with_file(&c_code, file_path)
1312}
1313
1314/// Transpile C code with file-based parsing for proper header resolution.
1315/// DECY-237: Uses clang's native file parsing instead of in-memory parsing.
1316fn transpile_with_file(c_code: &str, file_path: &Path) -> Result<String> {
1317    let base_dir = file_path.parent();
1318
1319    // Step 0: Preprocess #include directives (DECY-056) + Inject stdlib prototypes
1320    let stdlib_prototypes = StdlibPrototypes::new();
1321    let mut processed_files = std::collections::HashSet::new();
1322    let mut injected_headers = std::collections::HashSet::new();
1323    let _preprocessed = preprocess_includes(
1324        c_code,
1325        base_dir,
1326        &mut processed_files,
1327        &stdlib_prototypes,
1328        &mut injected_headers,
1329    )?;
1330
1331    // Step 1: Parse C code using file-based parsing for proper header resolution
1332    let parser = CParser::new().context("Failed to create C parser")?;
1333    let ast = parser.parse_file(file_path).context("Failed to parse C code")?;
1334
1335    // The rest is the same as transpile_with_includes
1336    process_ast_to_rust(ast, base_dir)
1337}
1338
1339/// Process an AST into Rust code (shared implementation)
1340fn process_ast_to_rust(ast: decy_parser::Ast, _base_dir: Option<&Path>) -> Result<String> {
1341    // Step 2: Convert to HIR
1342    let all_hir_functions: Vec<HirFunction> =
1343        ast.functions().iter().map(HirFunction::from_ast_function).collect();
1344
1345    // DECY-190: Deduplicate functions
1346    let hir_functions: Vec<HirFunction> = {
1347        use std::collections::HashMap;
1348        let mut func_map: HashMap<String, HirFunction> = HashMap::new();
1349
1350        for func in all_hir_functions {
1351            let name = func.name().to_string();
1352            if let Some(existing) = func_map.get(&name) {
1353                if func.has_body() && !existing.has_body() {
1354                    func_map.insert(name, func);
1355                }
1356            } else {
1357                func_map.insert(name, func);
1358            }
1359        }
1360
1361        func_map.into_values().collect()
1362    };
1363
1364    // Convert structs to HIR
1365    let hir_structs: Vec<decy_hir::HirStruct> = ast
1366        .structs()
1367        .iter()
1368        .map(|s| {
1369            let fields = s
1370                .fields()
1371                .iter()
1372                .map(|f| {
1373                    decy_hir::HirStructField::new(
1374                        f.name.clone(),
1375                        decy_hir::HirType::from_ast_type(&f.field_type),
1376                    )
1377                })
1378                .collect();
1379            decy_hir::HirStruct::new(s.name().to_string(), fields)
1380        })
1381        .collect();
1382
1383    // DECY-240: Convert enums to HIR
1384    let hir_enums: Vec<decy_hir::HirEnum> = ast
1385        .enums()
1386        .iter()
1387        .map(|e| {
1388            let variants = e
1389                .variants
1390                .iter()
1391                .map(|v| {
1392                    decy_hir::HirEnumVariant::new(v.name.clone(), v.value.map(|val| val as i32))
1393                })
1394                .collect();
1395            decy_hir::HirEnum::new(e.name.clone(), variants)
1396        })
1397        .collect();
1398
1399    // Convert global variables with deduplication
1400    let mut seen_globals = std::collections::HashSet::new();
1401    let hir_variables: Vec<decy_hir::HirStatement> = ast
1402        .variables()
1403        .iter()
1404        .filter(|v| {
1405            if seen_globals.contains(v.name()) {
1406                return false;
1407            }
1408            seen_globals.insert(v.name().to_string());
1409            true
1410        })
1411        .map(|v| decy_hir::HirStatement::VariableDeclaration {
1412            name: v.name().to_string(),
1413            var_type: decy_hir::HirType::from_ast_type(v.var_type()),
1414            initializer: v.initializer().map(decy_hir::HirExpression::from_ast_expression),
1415        })
1416        .collect();
1417
1418    // Convert typedefs
1419    let hir_typedefs: Vec<decy_hir::HirTypedef> = ast
1420        .typedefs()
1421        .iter()
1422        .map(|t| {
1423            decy_hir::HirTypedef::new(
1424                t.name().to_string(),
1425                decy_hir::HirType::from_ast_type(&t.underlying_type),
1426            )
1427        })
1428        .collect();
1429
1430    // Create code generator
1431    let code_generator = CodeGenerator::new();
1432    let mut rust_code = String::new();
1433
1434    // Track emitted items to avoid duplicates
1435    let mut emitted_structs = std::collections::HashSet::new();
1436    let mut emitted_typedefs = std::collections::HashSet::new();
1437
1438    // Generate struct definitions
1439    for hir_struct in &hir_structs {
1440        let struct_name = hir_struct.name();
1441        if emitted_structs.contains(struct_name) {
1442            continue;
1443        }
1444        emitted_structs.insert(struct_name.to_string());
1445        let struct_code = code_generator.generate_struct(hir_struct);
1446        rust_code.push_str(&struct_code);
1447        rust_code.push('\n');
1448    }
1449
1450    // DECY-240: Generate enum definitions (as const i32 values)
1451    for hir_enum in &hir_enums {
1452        let enum_code = code_generator.generate_enum(hir_enum);
1453        rust_code.push_str(&enum_code);
1454        rust_code.push('\n');
1455    }
1456
1457    // DECY-204: Generate C++ class definitions (struct + impl + Drop)
1458    let hir_classes: Vec<decy_hir::HirClass> = ast
1459        .classes()
1460        .iter()
1461        .map(decy_hir::HirClass::from_ast_class)
1462        .collect();
1463
1464    for hir_class in &hir_classes {
1465        let class_code = code_generator.generate_class(hir_class);
1466        rust_code.push_str(&class_code);
1467        rust_code.push('\n');
1468    }
1469
1470    // DECY-204: Generate C++ namespace definitions (mod blocks)
1471    let hir_namespaces: Vec<decy_hir::HirNamespace> = ast
1472        .namespaces()
1473        .iter()
1474        .map(decy_hir::HirNamespace::from_ast_namespace)
1475        .collect();
1476
1477    for hir_ns in &hir_namespaces {
1478        let ns_code = code_generator.generate_namespace(hir_ns);
1479        rust_code.push_str(&ns_code);
1480        rust_code.push('\n');
1481    }
1482
1483    // DECY-241: Add errno global variable (C compatibility)
1484    rust_code.push_str("static mut ERRNO: i32 = 0;\n");
1485
1486    // Generate typedefs
1487    for typedef in &hir_typedefs {
1488        let typedef_name = typedef.name();
1489        if emitted_typedefs.contains(typedef_name) {
1490            continue;
1491        }
1492        emitted_typedefs.insert(typedef_name.to_string());
1493        if let Ok(typedef_code) = code_generator.generate_typedef(typedef) {
1494            rust_code.push_str(&typedef_code);
1495            rust_code.push('\n');
1496        }
1497    }
1498
1499    // Generate global variables
1500    for var_stmt in &hir_variables {
1501        if let decy_hir::HirStatement::VariableDeclaration { name, var_type, initializer } =
1502            var_stmt
1503        {
1504            let type_str = CodeGenerator::map_type(var_type);
1505            if let Some(init_expr) = initializer {
1506                let init_code = code_generator.generate_expression(init_expr);
1507                rust_code
1508                    .push_str(&format!("static mut {}: {} = {};\n", name, type_str, init_code));
1509            } else {
1510                let default_value = match var_type {
1511                    decy_hir::HirType::Int => "0".to_string(),
1512                    decy_hir::HirType::UnsignedInt => "0".to_string(),
1513                    decy_hir::HirType::Char => "0".to_string(),
1514                    decy_hir::HirType::SignedChar => "0".to_string(), // DECY-250
1515                    decy_hir::HirType::Float => "0.0".to_string(),
1516                    decy_hir::HirType::Double => "0.0".to_string(),
1517                    decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
1518                    decy_hir::HirType::Array { element_type, size } => {
1519                        let elem_default = match element_type.as_ref() {
1520                            decy_hir::HirType::Char => "0u8",
1521                            decy_hir::HirType::SignedChar => "0i8", // DECY-250
1522                            decy_hir::HirType::Int => "0i32",
1523                            decy_hir::HirType::UnsignedInt => "0u32",
1524                            decy_hir::HirType::Float => "0.0f32",
1525                            decy_hir::HirType::Double => "0.0f64",
1526                            _ => "0",
1527                        };
1528                        if let Some(n) = size {
1529                            format!("[{}; {}]", elem_default, n)
1530                        } else {
1531                            format!("[{}; 0]", elem_default)
1532                        }
1533                    }
1534                    _ => "Default::default()".to_string(),
1535                };
1536                rust_code
1537                    .push_str(&format!("static mut {}: {} = {};\n", name, type_str, default_value));
1538            }
1539        }
1540    }
1541
1542    // Generate functions
1543    // DECY-248: Pass structs to enable sizeof(struct_field) type lookup
1544    for func in &hir_functions {
1545        rust_code.push_str(&code_generator.generate_function_with_structs(func, &hir_structs));
1546        rust_code.push('\n');
1547    }
1548
1549    Ok(rust_code)
1550}
1551
1552/// Transpile with Box transformation enabled.
1553///
1554/// This variant applies Box pattern detection to transform malloc/free
1555/// patterns into safe Box allocations.
1556///
1557/// # Examples
1558///
1559/// ```no_run
1560/// use decy_core::transpile_with_box_transform;
1561///
1562/// let c_code = r#"
1563///     int* create_value() {
1564///         int* p = malloc(sizeof(int));
1565///         *p = 42;
1566///         return p;
1567///     }
1568/// "#;
1569/// let rust_code = transpile_with_box_transform(c_code)?;
1570/// assert!(rust_code.contains("Box"));
1571/// # Ok::<(), anyhow::Error>(())
1572/// ```
1573pub fn transpile_with_box_transform(c_code: &str) -> Result<String> {
1574    contract_pre_configuration!();
1575    // Step 1: Parse C code
1576    let parser = CParser::new().context("Failed to create C parser")?;
1577    let ast = parser.parse(c_code).context("Failed to parse C code")?;
1578
1579    // Step 2: Convert to HIR
1580    let hir_functions: Vec<HirFunction> =
1581        ast.functions().iter().map(HirFunction::from_ast_function).collect();
1582
1583    // Step 3: Generate Rust code with Box transformation
1584    let code_generator = CodeGenerator::new();
1585    let pattern_detector = PatternDetector::new();
1586    let mut rust_code = String::new();
1587
1588    for func in &hir_functions {
1589        // Detect Box candidates in this function
1590        let candidates = pattern_detector.find_box_candidates(func);
1591
1592        let generated = code_generator.generate_function_with_box_transform(func, &candidates);
1593        rust_code.push_str(&generated);
1594        rust_code.push('\n');
1595    }
1596
1597    Ok(rust_code)
1598}
1599
1600/// Transpile a single C file with project context.
1601///
1602/// This enables file-by-file transpilation for incremental C→Rust migration.
1603/// The `ProjectContext` tracks types and functions across files for proper
1604/// reference resolution.
1605///
1606/// # Examples
1607///
1608/// ```no_run
1609/// use decy_core::{transpile_file, ProjectContext};
1610/// use std::path::Path;
1611///
1612/// let path = Path::new("src/utils.c");
1613/// let context = ProjectContext::new();
1614/// let result = transpile_file(path, &context)?;
1615///
1616/// assert!(!result.rust_code.is_empty());
1617/// # Ok::<(), anyhow::Error>(())
1618/// ```
1619///
1620/// # Errors
1621///
1622/// Returns an error if:
1623/// - File does not exist or cannot be read
1624/// - C code parsing fails
1625/// - Code generation fails
1626pub fn transpile_file(path: &Path, _context: &ProjectContext) -> Result<TranspiledFile> {
1627    contract_pre_configuration!();
1628    // Read the C source file
1629    let c_code = std::fs::read_to_string(path)
1630        .with_context(|| format!("Failed to read file: {}", path.display()))?;
1631
1632    // Parse dependencies from #include directives (simplified: just detect presence)
1633    let dependencies = extract_dependencies(path, &c_code)?;
1634
1635    // Transpile the C code using the main pipeline
1636    let rust_code = transpile(&c_code)?;
1637
1638    // Extract function names from the generated Rust code
1639    let functions_exported = extract_function_names(&rust_code);
1640
1641    // Generate FFI declarations for exported functions
1642    let ffi_declarations = generate_ffi_declarations(&functions_exported);
1643
1644    Ok(TranspiledFile::new(
1645        path.to_path_buf(),
1646        rust_code,
1647        dependencies,
1648        functions_exported,
1649        ffi_declarations,
1650    ))
1651}
1652
1653/// Extract dependencies from #include directives in C code.
1654///
1655/// This is a simplified implementation that detects #include directives
1656/// and resolves them relative to the source file's directory.
1657fn extract_dependencies(source_path: &Path, c_code: &str) -> Result<Vec<PathBuf>> {
1658    let mut dependencies = Vec::new();
1659    let source_dir = source_path
1660        .parent()
1661        .ok_or_else(|| anyhow::anyhow!("Source file has no parent directory"))?;
1662
1663    for line in c_code.lines() {
1664        let trimmed = line.trim();
1665        if trimmed.starts_with("#include") {
1666            // Extract header filename from #include "header.h" or #include <header.h>
1667            if let Some(start) = trimmed.find('"') {
1668                if let Some(end) = trimmed[start + 1..].find('"') {
1669                    let header_name = &trimmed[start + 1..start + 1 + end];
1670                    let header_path = source_dir.join(header_name);
1671                    if header_path.exists() {
1672                        dependencies.push(header_path);
1673                    }
1674                }
1675            }
1676        }
1677    }
1678
1679    Ok(dependencies)
1680}
1681
1682/// Extract function names from generated Rust code.
1683///
1684/// Parses function definitions to identify exported functions.
1685fn extract_function_names(rust_code: &str) -> Vec<String> {
1686    let mut functions = Vec::new();
1687
1688    for line in rust_code.lines() {
1689        let trimmed = line.trim();
1690        // Look for "fn function_name(" or "pub fn function_name("
1691        if (trimmed.starts_with("fn ") || trimmed.starts_with("pub fn ")) && trimmed.contains('(') {
1692            let start_idx = if trimmed.starts_with("pub fn ") {
1693                7 // length of "pub fn "
1694            } else {
1695                3 // length of "fn "
1696            };
1697
1698            if let Some(paren_idx) = trimmed[start_idx..].find('(') {
1699                let func_name = &trimmed[start_idx..start_idx + paren_idx];
1700                // Remove generic parameters if present (e.g., "foo<'a>" → "foo")
1701                let func_name_clean = if let Some(angle_idx) = func_name.find('<') {
1702                    &func_name[..angle_idx]
1703                } else {
1704                    func_name
1705                };
1706                functions.push(func_name_clean.trim().to_string());
1707            }
1708        }
1709    }
1710
1711    functions
1712}
1713
1714/// Generate FFI declarations for exported functions.
1715///
1716/// Creates extern "C" declarations to enable C↔Rust interoperability.
1717fn generate_ffi_declarations(functions: &[String]) -> String {
1718    if functions.is_empty() {
1719        return String::new();
1720    }
1721
1722    let mut ffi = String::from("// FFI declarations for C interoperability\n");
1723    ffi.push_str("#[no_mangle]\n");
1724    ffi.push_str("extern \"C\" {\n");
1725
1726    for func_name in functions {
1727        ffi.push_str(&format!("    // {}\n", func_name));
1728    }
1729
1730    ffi.push_str("}\n");
1731    ffi
1732}
1733
1734/// Check if a function parameter uses pointer arithmetic (DECY-125).
1735///
1736/// Returns true if the parameter is used in `p = p + n` or `p = p - n` patterns.
1737fn uses_pointer_arithmetic(func: &HirFunction, param_name: &str) -> bool {
1738    for stmt in func.body() {
1739        if statement_uses_pointer_arithmetic(stmt, param_name) {
1740            return true;
1741        }
1742    }
1743    false
1744}
1745
1746/// DECY-159: Check if a pointer parameter is compared to NULL.
1747///
1748/// If a pointer is compared to NULL, it means NULL is a valid input value.
1749/// Such parameters must remain as raw pointers, not references,
1750/// to avoid dereferencing NULL at call sites.
1751fn pointer_compared_to_null(func: &HirFunction, param_name: &str) -> bool {
1752    for stmt in func.body() {
1753        if statement_compares_to_null(stmt, param_name) {
1754            return true;
1755        }
1756    }
1757    false
1758}
1759
1760/// DECY-159: Recursively check if a statement compares a variable to NULL.
1761fn statement_compares_to_null(stmt: &HirStatement, var_name: &str) -> bool {
1762    match stmt {
1763        HirStatement::If { condition, then_block, else_block } => {
1764            // Check if condition is var == NULL or var != NULL
1765            if expression_compares_to_null(condition, var_name) {
1766                return true;
1767            }
1768            // Recurse into blocks
1769            then_block.iter().any(|s| statement_compares_to_null(s, var_name))
1770                || else_block
1771                    .as_ref()
1772                    .is_some_and(|blk| blk.iter().any(|s| statement_compares_to_null(s, var_name)))
1773        }
1774        HirStatement::While { condition, body } => {
1775            expression_compares_to_null(condition, var_name)
1776                || body.iter().any(|s| statement_compares_to_null(s, var_name))
1777        }
1778        HirStatement::For { condition, body, .. } => {
1779            condition.as_ref().is_some_and(|c| expression_compares_to_null(c, var_name))
1780                || body.iter().any(|s| statement_compares_to_null(s, var_name))
1781        }
1782        HirStatement::Switch { condition, cases, .. } => {
1783            expression_compares_to_null(condition, var_name)
1784                || cases
1785                    .iter()
1786                    .any(|c| c.body.iter().any(|s| statement_compares_to_null(s, var_name)))
1787        }
1788        _ => false,
1789    }
1790}
1791
1792/// DECY-159: Check if an expression compares a variable to NULL.
1793///
1794/// NULL in C can be:
1795/// - `NullLiteral` (explicit NULL)
1796/// - `IntLiteral(0)` (NULL macro expanded to 0)
1797/// - Cast of 0 to void* (less common)
1798fn expression_compares_to_null(expr: &HirExpression, var_name: &str) -> bool {
1799    use decy_hir::BinaryOperator;
1800    match expr {
1801        HirExpression::BinaryOp { op, left, right } => {
1802            // Check for var == NULL or var != NULL
1803            if matches!(op, BinaryOperator::Equal | BinaryOperator::NotEqual) {
1804                let left_is_var =
1805                    matches!(&**left, HirExpression::Variable(name) if name == var_name);
1806                let right_is_var =
1807                    matches!(&**right, HirExpression::Variable(name) if name == var_name);
1808                // NULL can be NullLiteral or IntLiteral(0) (when NULL macro expands to 0)
1809                let left_is_null =
1810                    matches!(&**left, HirExpression::NullLiteral | HirExpression::IntLiteral(0));
1811                let right_is_null =
1812                    matches!(&**right, HirExpression::NullLiteral | HirExpression::IntLiteral(0));
1813
1814                if (left_is_var && right_is_null) || (right_is_var && left_is_null) {
1815                    return true;
1816                }
1817            }
1818            // Recurse into binary op children for nested comparisons
1819            expression_compares_to_null(left, var_name)
1820                || expression_compares_to_null(right, var_name)
1821        }
1822        HirExpression::UnaryOp { operand, .. } => expression_compares_to_null(operand, var_name),
1823        _ => false,
1824    }
1825}
1826
1827/// Recursively check if a statement uses pointer arithmetic on a variable (DECY-125).
1828fn statement_uses_pointer_arithmetic(stmt: &HirStatement, var_name: &str) -> bool {
1829    use decy_hir::BinaryOperator;
1830    match stmt {
1831        HirStatement::Assignment { target, value } => {
1832            // Check if this is var = var + n or var = var - n (pointer arithmetic)
1833            if target == var_name {
1834                if let HirExpression::BinaryOp { op, left, .. } = value {
1835                    if matches!(op, BinaryOperator::Add | BinaryOperator::Subtract) {
1836                        if let HirExpression::Variable(name) = &**left {
1837                            if name == var_name {
1838                                return true;
1839                            }
1840                        }
1841                    }
1842                }
1843            }
1844            false
1845        }
1846        HirStatement::If { then_block, else_block, .. } => {
1847            then_block.iter().any(|s| statement_uses_pointer_arithmetic(s, var_name))
1848                || else_block.as_ref().is_some_and(|blk| {
1849                    blk.iter().any(|s| statement_uses_pointer_arithmetic(s, var_name))
1850                })
1851        }
1852        HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
1853            body.iter().any(|s| statement_uses_pointer_arithmetic(s, var_name))
1854        }
1855        _ => false,
1856    }
1857}
1858
1859
1860#[cfg(test)]
1861#[path = "tests.rs"]
1862mod tests;