1#![warn(missing_docs)]
12#![warn(clippy::all)]
13#![deny(unsafe_code)]
14
15pub mod metrics;
16
17pub use metrics::{CompileMetrics, TranspilationResult};
18
19use anyhow::{Context, Result};
20use decy_analyzer::patterns::PatternDetector;
21use decy_codegen::CodeGenerator;
22use decy_hir::{HirExpression, HirFunction, HirStatement};
23use decy_ownership::{
24 array_slice::ArrayParameterTransformer, borrow_gen::BorrowGenerator,
25 classifier_integration::classify_with_rules, dataflow::DataflowAnalyzer,
26 lifetime::LifetimeAnalyzer, lifetime_gen::LifetimeAnnotator,
27};
28use decy_parser::parser::CParser;
29use decy_stdlib::StdlibPrototypes;
30use petgraph::graph::{DiGraph, NodeIndex};
31use petgraph::visit::Topo;
32use std::collections::HashMap;
33use std::path::{Path, PathBuf};
34
35#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
40pub struct TranspiledFile {
41 pub source_path: PathBuf,
43
44 pub rust_code: String,
46
47 pub dependencies: Vec<PathBuf>,
49
50 pub functions_exported: Vec<String>,
52
53 pub ffi_declarations: String,
55}
56
57impl TranspiledFile {
58 pub fn new(
60 source_path: PathBuf,
61 rust_code: String,
62 dependencies: Vec<PathBuf>,
63 functions_exported: Vec<String>,
64 ffi_declarations: String,
65 ) -> Self {
66 Self {
67 source_path,
68 rust_code,
69 dependencies,
70 functions_exported,
71 ffi_declarations,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Default)]
81pub struct ProjectContext {
82 types: HashMap<String, String>,
84
85 functions: HashMap<String, String>,
87
88 transpiled_files: HashMap<PathBuf, TranspiledFile>,
90}
91
92impl ProjectContext {
93 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn add_transpiled_file(&mut self, file: &TranspiledFile) {
103 self.transpiled_files
105 .insert(file.source_path.clone(), file.clone());
106
107 if file.rust_code.contains("struct") {
110 for line in file.rust_code.lines() {
112 if line.contains("struct") {
113 if let Some(name) = self.extract_type_name(line) {
114 self.types.insert(name.clone(), line.to_string());
115 }
116 }
117 }
118 }
119
120 for func_name in &file.functions_exported {
122 self.functions.insert(
123 func_name.clone(),
124 file.source_path.to_string_lossy().to_string(),
125 );
126 }
127 }
128
129 pub fn has_type(&self, type_name: &str) -> bool {
131 self.types.contains_key(type_name)
132 }
133
134 pub fn has_function(&self, func_name: &str) -> bool {
136 self.functions.contains_key(func_name)
137 }
138
139 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 fn extract_type_name(&self, line: &str) -> Option<String> {
146 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#[derive(Debug, Clone)]
163pub struct DependencyGraph {
164 graph: DiGraph<PathBuf, ()>,
166
167 path_to_node: HashMap<PathBuf, NodeIndex>,
169}
170
171impl DependencyGraph {
172 pub fn new() -> Self {
174 Self {
175 graph: DiGraph::new(),
176 path_to_node: HashMap::new(),
177 }
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.graph.node_count() == 0
183 }
184
185 pub fn file_count(&self) -> usize {
187 self.graph.node_count()
188 }
189
190 pub fn contains_file(&self, path: &Path) -> bool {
192 self.path_to_node.contains_key(path)
193 }
194
195 pub fn add_file(&mut self, path: &Path) {
199 if !self.contains_file(path) {
200 let node = self.graph.add_node(path.to_path_buf());
201 self.path_to_node.insert(path.to_path_buf(), node);
202 }
203 }
204
205 pub fn add_dependency(&mut self, from: &Path, to: &Path) {
209 let from_node = *self
210 .path_to_node
211 .get(from)
212 .expect("from file must be added to graph first");
213 let to_node = *self
214 .path_to_node
215 .get(to)
216 .expect("to file must be added to graph first");
217
218 self.graph.add_edge(from_node, to_node, ());
219 }
220
221 pub fn has_dependency(&self, from: &Path, to: &Path) -> bool {
223 if let (Some(&from_node), Some(&to_node)) =
224 (self.path_to_node.get(from), self.path_to_node.get(to))
225 {
226 self.graph.contains_edge(from_node, to_node)
227 } else {
228 false
229 }
230 }
231
232 pub fn topological_sort(&self) -> Result<Vec<PathBuf>> {
237 if petgraph::algo::is_cyclic_directed(&self.graph) {
239 return Err(anyhow::anyhow!(
240 "Circular dependency detected in file dependencies"
241 ));
242 }
243
244 let mut topo = Topo::new(&self.graph);
245 let mut build_order = Vec::new();
246
247 while let Some(node) = topo.next(&self.graph) {
248 if let Some(path) = self.graph.node_weight(node) {
249 build_order.push(path.clone());
250 }
251 }
252
253 build_order.reverse();
255
256 Ok(build_order)
257 }
258
259 pub fn from_files(files: &[PathBuf]) -> Result<Self> {
263 let mut graph = Self::new();
264
265 for file in files {
267 graph.add_file(file);
268 }
269
270 for file in files {
272 let content = std::fs::read_to_string(file)
273 .with_context(|| format!("Failed to read file: {}", file.display()))?;
274
275 let includes = Self::parse_include_directives(&content);
276
277 let file_dir = file.parent().unwrap_or_else(|| Path::new("."));
279
280 for include in includes {
281 let include_path = file_dir.join(&include);
282
283 if graph.contains_file(&include_path) {
285 graph.add_dependency(file, &include_path);
286 }
287 }
288 }
289
290 Ok(graph)
291 }
292
293 pub fn parse_include_directives(code: &str) -> Vec<String> {
297 let mut includes = Vec::new();
298
299 for line in code.lines() {
300 let trimmed = line.trim();
301 if trimmed.starts_with("#include") {
302 if let Some(start) = trimmed.find('"').or_else(|| trimmed.find('<')) {
304 let end_char = if trimmed.chars().nth(start) == Some('"') {
305 '"'
306 } else {
307 '>'
308 };
309 if let Some(end) = trimmed[start + 1..].find(end_char) {
310 let filename = &trimmed[start + 1..start + 1 + end];
311 includes.push(filename.to_string());
312 }
313 }
314 }
315 }
316
317 includes
318 }
319
320 pub fn has_header_guard(path: &Path) -> Result<bool> {
322 let content = std::fs::read_to_string(path)
323 .with_context(|| format!("Failed to read file: {}", path.display()))?;
324
325 let has_ifndef = content.lines().any(|line| {
326 let trimmed = line.trim();
327 trimmed.starts_with("#ifndef") || trimmed.starts_with("#if !defined")
328 });
329
330 let has_define = content
331 .lines()
332 .any(|line| line.trim().starts_with("#define"));
333 let has_endif = content
334 .lines()
335 .any(|line| line.trim().starts_with("#endif"));
336
337 Ok(has_ifndef && has_define && has_endif)
338 }
339}
340
341impl Default for DependencyGraph {
342 fn default() -> Self {
343 Self::new()
344 }
345}
346
347#[derive(Debug, Clone)]
349pub struct CacheStatistics {
350 pub hits: usize,
352 pub misses: usize,
354 pub total_files: usize,
356}
357
358#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
360struct CacheEntry {
361 hash: String,
363 transpiled: TranspiledFile,
365 dependency_hashes: HashMap<PathBuf, String>,
367}
368
369#[derive(Debug, Clone)]
395pub struct TranspilationCache {
396 entries: HashMap<PathBuf, CacheEntry>,
398 cache_dir: Option<PathBuf>,
400 hits: usize,
402 misses: usize,
403}
404
405impl TranspilationCache {
406 pub fn new() -> Self {
408 Self {
409 entries: HashMap::new(),
410 cache_dir: None,
411 hits: 0,
412 misses: 0,
413 }
414 }
415
416 pub fn with_directory(cache_dir: &Path) -> Self {
418 Self {
419 entries: HashMap::new(),
420 cache_dir: Some(cache_dir.to_path_buf()),
421 hits: 0,
422 misses: 0,
423 }
424 }
425
426 pub fn compute_hash(&self, path: &Path) -> Result<String> {
430 use sha2::{Digest, Sha256};
431
432 let content = std::fs::read(path)
433 .with_context(|| format!("Failed to read file for hashing: {}", path.display()))?;
434
435 let mut hasher = Sha256::new();
436 hasher.update(&content);
437 let result = hasher.finalize();
438
439 Ok(format!("{:x}", result))
440 }
441
442 pub fn insert(&mut self, path: &Path, transpiled: &TranspiledFile) {
444 let hash = match self.compute_hash(path) {
445 Ok(h) => h,
446 Err(_) => return, };
448
449 let mut dependency_hashes = HashMap::new();
451 for dep_path in &transpiled.dependencies {
452 if let Ok(dep_hash) = self.compute_hash(dep_path) {
453 dependency_hashes.insert(dep_path.clone(), dep_hash);
454 }
455 }
456
457 let entry = CacheEntry {
458 hash,
459 transpiled: transpiled.clone(),
460 dependency_hashes,
461 };
462
463 self.entries.insert(path.to_path_buf(), entry);
464 }
465
466 pub fn get(&mut self, path: &Path) -> Option<&TranspiledFile> {
473 let entry = self.entries.get(&path.to_path_buf())?;
474
475 let current_hash = self.compute_hash(path).ok()?;
477 if current_hash != entry.hash {
478 self.misses += 1;
479 return None;
480 }
481
482 for (dep_path, cached_hash) in &entry.dependency_hashes {
484 if let Ok(current_dep_hash) = self.compute_hash(dep_path) {
485 if ¤t_dep_hash != cached_hash {
486 self.misses += 1;
487 return None;
488 }
489 }
490 }
491
492 self.hits += 1;
493 Some(&entry.transpiled)
494 }
495
496 pub fn save(&self) -> Result<()> {
498 let cache_dir = self
499 .cache_dir
500 .as_ref()
501 .ok_or_else(|| anyhow::anyhow!("Cache directory not set"))?;
502
503 std::fs::create_dir_all(cache_dir).with_context(|| {
504 format!("Failed to create cache directory: {}", cache_dir.display())
505 })?;
506
507 let cache_file = cache_dir.join("cache.json");
508 let json =
509 serde_json::to_string_pretty(&self.entries).context("Failed to serialize cache")?;
510
511 std::fs::write(&cache_file, json)
512 .with_context(|| format!("Failed to write cache file: {}", cache_file.display()))?;
513
514 Ok(())
515 }
516
517 pub fn load(cache_dir: &Path) -> Result<Self> {
519 let cache_file = cache_dir.join("cache.json");
520
521 if !cache_file.exists() {
522 return Ok(Self::with_directory(cache_dir));
524 }
525
526 let json = std::fs::read_to_string(&cache_file)
527 .with_context(|| format!("Failed to read cache file: {}", cache_file.display()))?;
528
529 let entries: HashMap<PathBuf, CacheEntry> =
530 serde_json::from_str(&json).context("Failed to deserialize cache")?;
531
532 Ok(Self {
533 entries,
534 cache_dir: Some(cache_dir.to_path_buf()),
535 hits: 0,
536 misses: 0,
537 })
538 }
539
540 pub fn clear(&mut self) {
542 self.entries.clear();
543 self.hits = 0;
544 self.misses = 0;
545 }
546
547 pub fn statistics(&self) -> CacheStatistics {
549 CacheStatistics {
550 hits: self.hits,
551 misses: self.misses,
552 total_files: self.entries.len(),
553 }
554 }
555}
556
557impl Default for TranspilationCache {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563fn preprocess_includes(
584 source: &str,
585 base_dir: Option<&Path>,
586 processed: &mut std::collections::HashSet<PathBuf>,
587 stdlib_prototypes: &StdlibPrototypes,
588 injected_headers: &mut std::collections::HashSet<String>,
589) -> Result<String> {
590 let mut result = String::new();
591 let base_dir = base_dir.unwrap_or_else(|| Path::new("."));
592
593 for line in source.lines() {
594 let trimmed = line.trim();
595
596 if trimmed.starts_with("#include") {
598 let (filename, is_system) = if let Some(start) = trimmed.find('"') {
600 if let Some(end) = trimmed[start + 1..].find('"') {
601 let filename = &trimmed[start + 1..start + 1 + end];
602 (filename, false)
603 } else {
604 result.push_str(line);
606 result.push('\n');
607 continue;
608 }
609 } else if let Some(start) = trimmed.find('<') {
610 if let Some(end) = trimmed[start + 1..].find('>') {
611 let filename = &trimmed[start + 1..start + 1 + end];
612 (filename, true)
613 } else {
614 result.push_str(line);
616 result.push('\n');
617 continue;
618 }
619 } else {
620 result.push_str(line);
622 result.push('\n');
623 continue;
624 };
625
626 if is_system {
629 result.push_str(&format!("// {}\n", line));
631
632 if !injected_headers.contains(filename) {
634 injected_headers.insert(filename.to_string());
636
637 if let Some(header) = decy_stdlib::StdHeader::from_filename(filename) {
639 result
640 .push_str(&format!("// BEGIN: Built-in prototypes for {}\n", filename));
641 result.push_str(&stdlib_prototypes.inject_prototypes_for_header(header));
642 result.push_str(&format!("// END: Built-in prototypes for {}\n", filename));
643 } else {
644 result.push_str(&format!("// Unknown system header: {}\n", filename));
646 }
647 }
648
649 continue;
650 }
651
652 let include_path = base_dir.join(filename);
654
655 if processed.contains(&include_path) {
657 result.push_str(&format!("// Already included: {}\n", filename));
659 continue;
660 }
661
662 if let Ok(included_content) = std::fs::read_to_string(&include_path) {
664 processed.insert(include_path.clone());
666
667 let included_dir = include_path.parent().unwrap_or(base_dir);
669
670 let preprocessed = preprocess_includes(
672 &included_content,
673 Some(included_dir),
674 processed,
675 stdlib_prototypes,
676 injected_headers,
677 )?;
678
679 result.push_str(&format!("// BEGIN INCLUDE: {}\n", filename));
681 result.push_str(&preprocessed);
682 result.push_str(&format!("// END INCLUDE: {}\n", filename));
683 } else {
684 anyhow::bail!("Failed to find include file: {}", include_path.display());
686 }
687 } else {
688 result.push_str(line);
690 result.push('\n');
691 }
692 }
693
694 Ok(result)
695}
696
697pub fn transpile(c_code: &str) -> Result<String> {
723 transpile_with_includes(c_code, None)
724}
725
726pub fn transpile_with_verification(c_code: &str) -> Result<TranspilationResult> {
750 match transpile(c_code) {
751 Ok(rust_code) => Ok(TranspilationResult::success(rust_code)),
752 Err(e) => {
753 Ok(TranspilationResult::failure(
755 String::new(),
756 vec![e.to_string()],
757 ))
758 }
759 }
760}
761
762pub fn transpile_with_includes(c_code: &str, base_dir: Option<&Path>) -> Result<String> {
780 let stdlib_prototypes = StdlibPrototypes::new();
782 let mut processed_files = std::collections::HashSet::new();
783 let mut injected_headers = std::collections::HashSet::new();
784 let preprocessed = preprocess_includes(
785 c_code,
786 base_dir,
787 &mut processed_files,
788 &stdlib_prototypes,
789 &mut injected_headers,
790 )?;
791
792 let parser = CParser::new().context("Failed to create C parser")?;
798 let ast = parser
799 .parse(&preprocessed)
800 .context("Failed to parse C code")?;
801
802 let all_hir_functions: Vec<HirFunction> = ast
804 .functions()
805 .iter()
806 .map(HirFunction::from_ast_function)
807 .collect();
808
809 let hir_functions: Vec<HirFunction> = {
813 use std::collections::HashMap;
814 let mut func_map: HashMap<String, HirFunction> = HashMap::new();
815
816 for func in all_hir_functions {
817 let name = func.name().to_string();
818 if let Some(existing) = func_map.get(&name) {
819 if func.has_body() && !existing.has_body() {
821 func_map.insert(name, func);
822 }
823 } else {
825 func_map.insert(name, func);
826 }
827 }
828
829 func_map.into_values().collect()
831 };
832
833 let hir_structs: Vec<decy_hir::HirStruct> = ast
835 .structs()
836 .iter()
837 .map(|s| {
838 let fields = s
839 .fields
840 .iter()
841 .map(|f| {
842 decy_hir::HirStructField::new(
843 f.name.clone(),
844 decy_hir::HirType::from_ast_type(&f.field_type),
845 )
846 })
847 .collect();
848 decy_hir::HirStruct::new(s.name.clone(), fields)
849 })
850 .collect();
851
852 let hir_variables: Vec<decy_hir::HirStatement> = ast
854 .variables()
855 .iter()
856 .map(|v| decy_hir::HirStatement::VariableDeclaration {
857 name: v.name().to_string(),
858 var_type: decy_hir::HirType::from_ast_type(v.var_type()),
859 initializer: v
860 .initializer()
861 .map(decy_hir::HirExpression::from_ast_expression),
862 })
863 .collect();
864
865 let hir_typedefs: Vec<decy_hir::HirTypedef> = ast
867 .typedefs()
868 .iter()
869 .map(|t| {
870 decy_hir::HirTypedef::new(
871 t.name().to_string(),
872 decy_hir::HirType::from_ast_type(&t.underlying_type),
873 )
874 })
875 .collect();
876
877 let slice_func_args: Vec<(String, Vec<(usize, usize)>)> = hir_functions
879 .iter()
880 .filter_map(|func| {
881 let mut mappings = Vec::new();
882 let params = func.parameters();
883
884 for (i, param) in params.iter().enumerate() {
885 if matches!(param.param_type(), decy_hir::HirType::Pointer(_)) {
887 if i + 1 < params.len() {
889 let next_param = ¶ms[i + 1];
890 if matches!(next_param.param_type(), decy_hir::HirType::Int) {
891 let param_name = next_param.name().to_lowercase();
892 if param_name.contains("len")
893 || param_name.contains("size")
894 || param_name.contains("count")
895 || param_name == "n"
896 || param_name == "num"
897 {
898 mappings.push((i, i + 1));
899 }
900 }
901 }
902 }
903 }
904
905 if mappings.is_empty() {
906 None
907 } else {
908 Some((func.name().to_string(), mappings))
909 }
910 })
911 .collect();
912
913 let mut transformed_functions = Vec::new();
915
916 for func in hir_functions {
917 let dataflow_analyzer = DataflowAnalyzer::new();
919 let dataflow_graph = dataflow_analyzer.analyze(&func);
920
921 let ownership_inferences = classify_with_rules(&dataflow_graph, &func);
924
925 let borrow_generator = BorrowGenerator::new();
927 let func_with_borrows = borrow_generator.transform_function(&func, &ownership_inferences);
928
929 let array_transformer = ArrayParameterTransformer::new();
931 let func_with_slices = array_transformer.transform(&func_with_borrows, &dataflow_graph);
932
933 let lifetime_analyzer = LifetimeAnalyzer::new();
935 let scope_tree = lifetime_analyzer.build_scope_tree(&func_with_slices);
936 let _lifetimes = lifetime_analyzer.track_lifetimes(&func_with_slices, &scope_tree);
937
938 let lifetime_annotator = LifetimeAnnotator::new();
940 let annotated_signature = lifetime_annotator.annotate_function(&func_with_slices);
941
942 transformed_functions.push((func_with_slices, annotated_signature));
944 }
945
946 let code_generator = CodeGenerator::new();
948 let mut rust_code = String::new();
949
950 let mut emitted_structs = std::collections::HashSet::new();
952 let mut emitted_typedefs = std::collections::HashSet::new();
953
954 for hir_struct in &hir_structs {
956 let struct_name = hir_struct.name();
957 if emitted_structs.contains(struct_name) {
958 continue; }
960 emitted_structs.insert(struct_name.to_string());
961
962 let struct_code = code_generator.generate_struct(hir_struct);
963 rust_code.push_str(&struct_code);
964 rust_code.push('\n');
965 }
966
967 for typedef in &hir_typedefs {
969 let typedef_name = typedef.name();
970 if emitted_typedefs.contains(typedef_name) {
971 continue; }
973 emitted_typedefs.insert(typedef_name.to_string());
974
975 if let Ok(typedef_code) = code_generator.generate_typedef(typedef) {
976 rust_code.push_str(&typedef_code);
977 rust_code.push('\n');
978 }
979 }
980
981 for var_stmt in &hir_variables {
983 if let decy_hir::HirStatement::VariableDeclaration {
984 name,
985 var_type,
986 initializer,
987 } = var_stmt
988 {
989 let type_str = CodeGenerator::map_type(var_type);
991
992 if let Some(init_expr) = initializer {
993 let init_code = if let decy_hir::HirType::Array {
995 element_type,
996 size: Some(size_val),
997 } = var_type
998 {
999 if let decy_hir::HirExpression::IntLiteral(n) = init_expr {
1001 if *n as usize == *size_val {
1002 let element_init = match element_type.as_ref() {
1004 decy_hir::HirType::Char => "0u8".to_string(),
1005 decy_hir::HirType::Int => "0i32".to_string(),
1006 decy_hir::HirType::Float => "0.0f32".to_string(),
1007 decy_hir::HirType::Double => "0.0f64".to_string(),
1008 _ => "0".to_string(),
1009 };
1010 format!("[{}; {}]", element_init, size_val)
1011 } else {
1012 code_generator.generate_expression(init_expr)
1013 }
1014 } else {
1015 code_generator.generate_expression(init_expr)
1016 }
1017 } else {
1018 code_generator.generate_expression(init_expr)
1019 };
1020 rust_code.push_str(&format!(
1021 "static mut {}: {} = {};\n",
1022 name, type_str, init_code
1023 ));
1024 } else {
1025 let default_value = match var_type {
1028 decy_hir::HirType::Int => "0".to_string(),
1029 decy_hir::HirType::UnsignedInt => "0".to_string(),
1030 decy_hir::HirType::Char => "0".to_string(),
1031 decy_hir::HirType::Float => "0.0".to_string(),
1032 decy_hir::HirType::Double => "0.0".to_string(),
1033 decy_hir::HirType::Pointer(_) => "std::ptr::null_mut()".to_string(),
1034 decy_hir::HirType::Array { element_type, size } => {
1036 let elem_default = match element_type.as_ref() {
1037 decy_hir::HirType::Char => "0u8",
1038 decy_hir::HirType::Int => "0i32",
1039 decy_hir::HirType::UnsignedInt => "0u32",
1040 decy_hir::HirType::Float => "0.0f32",
1041 decy_hir::HirType::Double => "0.0f64",
1042 _ => "0",
1043 };
1044 if let Some(n) = size {
1045 format!("[{}; {}]", elem_default, n)
1046 } else {
1047 format!("[{}; 0]", elem_default)
1048 }
1049 }
1050 decy_hir::HirType::FunctionPointer { .. } => {
1051 rust_code.push_str(&format!(
1053 "static mut {}: Option<{}> = None;\n",
1054 name, type_str
1055 ));
1056 continue;
1057 }
1058 _ => "Default::default()".to_string(),
1059 };
1060 rust_code.push_str(&format!(
1061 "static mut {}: {} = {};\n",
1062 name, type_str, default_value
1063 ));
1064 }
1065 }
1066 }
1067 if !hir_variables.is_empty() {
1068 rust_code.push('\n');
1069 }
1070
1071 let all_function_sigs: Vec<(String, Vec<decy_hir::HirType>)> = transformed_functions
1075 .iter()
1076 .map(|(func, _sig)| {
1077 let param_types: Vec<decy_hir::HirType> = func
1078 .parameters()
1079 .iter()
1080 .map(|p| {
1081 if let decy_hir::HirType::Pointer(inner) = p.param_type() {
1085 if uses_pointer_arithmetic(func, p.name())
1087 || pointer_compared_to_null(func, p.name())
1088 {
1089 p.param_type().clone()
1091 } else {
1092 decy_hir::HirType::Reference {
1093 inner: inner.clone(),
1094 mutable: true,
1095 }
1096 }
1097 } else {
1098 p.param_type().clone()
1099 }
1100 })
1101 .collect();
1102 (func.name().to_string(), param_types)
1103 })
1104 .collect();
1105
1106 let string_iter_funcs: Vec<(String, Vec<(usize, bool)>)> = transformed_functions
1108 .iter()
1109 .filter_map(|(func, _)| {
1110 let params = code_generator.get_string_iteration_params(func);
1111 if params.is_empty() {
1112 None
1113 } else {
1114 Some((func.name().to_string(), params))
1115 }
1116 })
1117 .collect();
1118
1119 for (func, annotated_sig) in &transformed_functions {
1122 let generated = code_generator.generate_function_with_lifetimes_and_structs(
1123 func,
1124 annotated_sig,
1125 &hir_structs,
1126 &all_function_sigs,
1127 &slice_func_args,
1128 &string_iter_funcs,
1129 );
1130 rust_code.push_str(&generated);
1131 rust_code.push('\n');
1132 }
1133
1134 Ok(rust_code)
1135}
1136
1137pub fn transpile_with_box_transform(c_code: &str) -> Result<String> {
1159 let parser = CParser::new().context("Failed to create C parser")?;
1161 let ast = parser.parse(c_code).context("Failed to parse C code")?;
1162
1163 let hir_functions: Vec<HirFunction> = ast
1165 .functions()
1166 .iter()
1167 .map(HirFunction::from_ast_function)
1168 .collect();
1169
1170 let code_generator = CodeGenerator::new();
1172 let pattern_detector = PatternDetector::new();
1173 let mut rust_code = String::new();
1174
1175 for func in &hir_functions {
1176 let candidates = pattern_detector.find_box_candidates(func);
1178
1179 let generated = code_generator.generate_function_with_box_transform(func, &candidates);
1180 rust_code.push_str(&generated);
1181 rust_code.push('\n');
1182 }
1183
1184 Ok(rust_code)
1185}
1186
1187pub fn transpile_file(path: &Path, _context: &ProjectContext) -> Result<TranspiledFile> {
1214 let c_code = std::fs::read_to_string(path)
1216 .with_context(|| format!("Failed to read file: {}", path.display()))?;
1217
1218 let dependencies = extract_dependencies(path, &c_code)?;
1220
1221 let rust_code = transpile(&c_code)?;
1223
1224 let functions_exported = extract_function_names(&rust_code);
1226
1227 let ffi_declarations = generate_ffi_declarations(&functions_exported);
1229
1230 Ok(TranspiledFile::new(
1231 path.to_path_buf(),
1232 rust_code,
1233 dependencies,
1234 functions_exported,
1235 ffi_declarations,
1236 ))
1237}
1238
1239fn extract_dependencies(source_path: &Path, c_code: &str) -> Result<Vec<PathBuf>> {
1244 let mut dependencies = Vec::new();
1245 let source_dir = source_path
1246 .parent()
1247 .ok_or_else(|| anyhow::anyhow!("Source file has no parent directory"))?;
1248
1249 for line in c_code.lines() {
1250 let trimmed = line.trim();
1251 if trimmed.starts_with("#include") {
1252 if let Some(start) = trimmed.find('"') {
1254 if let Some(end) = trimmed[start + 1..].find('"') {
1255 let header_name = &trimmed[start + 1..start + 1 + end];
1256 let header_path = source_dir.join(header_name);
1257 if header_path.exists() {
1258 dependencies.push(header_path);
1259 }
1260 }
1261 }
1262 }
1263 }
1264
1265 Ok(dependencies)
1266}
1267
1268fn extract_function_names(rust_code: &str) -> Vec<String> {
1272 let mut functions = Vec::new();
1273
1274 for line in rust_code.lines() {
1275 let trimmed = line.trim();
1276 if (trimmed.starts_with("fn ") || trimmed.starts_with("pub fn ")) && trimmed.contains('(') {
1278 let start_idx = if trimmed.starts_with("pub fn ") {
1279 7 } else {
1281 3 };
1283
1284 if let Some(paren_idx) = trimmed[start_idx..].find('(') {
1285 let func_name = &trimmed[start_idx..start_idx + paren_idx];
1286 let func_name_clean = if let Some(angle_idx) = func_name.find('<') {
1288 &func_name[..angle_idx]
1289 } else {
1290 func_name
1291 };
1292 functions.push(func_name_clean.trim().to_string());
1293 }
1294 }
1295 }
1296
1297 functions
1298}
1299
1300fn generate_ffi_declarations(functions: &[String]) -> String {
1304 if functions.is_empty() {
1305 return String::new();
1306 }
1307
1308 let mut ffi = String::from("// FFI declarations for C interoperability\n");
1309 ffi.push_str("#[no_mangle]\n");
1310 ffi.push_str("extern \"C\" {\n");
1311
1312 for func_name in functions {
1313 ffi.push_str(&format!(" // {}\n", func_name));
1314 }
1315
1316 ffi.push_str("}\n");
1317 ffi
1318}
1319
1320fn uses_pointer_arithmetic(func: &HirFunction, param_name: &str) -> bool {
1324 for stmt in func.body() {
1325 if statement_uses_pointer_arithmetic(stmt, param_name) {
1326 return true;
1327 }
1328 }
1329 false
1330}
1331
1332fn pointer_compared_to_null(func: &HirFunction, param_name: &str) -> bool {
1338 for stmt in func.body() {
1339 if statement_compares_to_null(stmt, param_name) {
1340 return true;
1341 }
1342 }
1343 false
1344}
1345
1346fn statement_compares_to_null(stmt: &HirStatement, var_name: &str) -> bool {
1348 match stmt {
1349 HirStatement::If {
1350 condition,
1351 then_block,
1352 else_block,
1353 } => {
1354 if expression_compares_to_null(condition, var_name) {
1356 return true;
1357 }
1358 then_block
1360 .iter()
1361 .any(|s| statement_compares_to_null(s, var_name))
1362 || else_block
1363 .as_ref()
1364 .is_some_and(|blk| blk.iter().any(|s| statement_compares_to_null(s, var_name)))
1365 }
1366 HirStatement::While { condition, body } => {
1367 expression_compares_to_null(condition, var_name)
1368 || body.iter().any(|s| statement_compares_to_null(s, var_name))
1369 }
1370 HirStatement::For {
1371 condition, body, ..
1372 } => {
1373 expression_compares_to_null(condition, var_name)
1374 || body.iter().any(|s| statement_compares_to_null(s, var_name))
1375 }
1376 HirStatement::Switch {
1377 condition, cases, ..
1378 } => {
1379 expression_compares_to_null(condition, var_name)
1380 || cases.iter().any(|c| {
1381 c.body
1382 .iter()
1383 .any(|s| statement_compares_to_null(s, var_name))
1384 })
1385 }
1386 _ => false,
1387 }
1388}
1389
1390fn expression_compares_to_null(expr: &HirExpression, var_name: &str) -> bool {
1397 use decy_hir::BinaryOperator;
1398 match expr {
1399 HirExpression::BinaryOp { op, left, right } => {
1400 if matches!(op, BinaryOperator::Equal | BinaryOperator::NotEqual) {
1402 let left_is_var =
1403 matches!(&**left, HirExpression::Variable(name) if name == var_name);
1404 let right_is_var =
1405 matches!(&**right, HirExpression::Variable(name) if name == var_name);
1406 let left_is_null = matches!(
1408 &**left,
1409 HirExpression::NullLiteral | HirExpression::IntLiteral(0)
1410 );
1411 let right_is_null = matches!(
1412 &**right,
1413 HirExpression::NullLiteral | HirExpression::IntLiteral(0)
1414 );
1415
1416 if (left_is_var && right_is_null) || (right_is_var && left_is_null) {
1417 return true;
1418 }
1419 }
1420 expression_compares_to_null(left, var_name)
1422 || expression_compares_to_null(right, var_name)
1423 }
1424 HirExpression::UnaryOp { operand, .. } => expression_compares_to_null(operand, var_name),
1425 _ => false,
1426 }
1427}
1428
1429fn statement_uses_pointer_arithmetic(stmt: &HirStatement, var_name: &str) -> bool {
1431 use decy_hir::BinaryOperator;
1432 match stmt {
1433 HirStatement::Assignment { target, value } => {
1434 if target == var_name {
1436 if let HirExpression::BinaryOp { op, left, .. } = value {
1437 if matches!(op, BinaryOperator::Add | BinaryOperator::Subtract) {
1438 if let HirExpression::Variable(name) = &**left {
1439 if name == var_name {
1440 return true;
1441 }
1442 }
1443 }
1444 }
1445 }
1446 false
1447 }
1448 HirStatement::If {
1449 then_block,
1450 else_block,
1451 ..
1452 } => {
1453 then_block
1454 .iter()
1455 .any(|s| statement_uses_pointer_arithmetic(s, var_name))
1456 || else_block.as_ref().is_some_and(|blk| {
1457 blk.iter()
1458 .any(|s| statement_uses_pointer_arithmetic(s, var_name))
1459 })
1460 }
1461 HirStatement::While { body, .. } | HirStatement::For { body, .. } => body
1462 .iter()
1463 .any(|s| statement_uses_pointer_arithmetic(s, var_name)),
1464 _ => false,
1465 }
1466}
1467
1468#[cfg(test)]
1469mod tests {
1470 use super::*;
1471
1472 #[test]
1473 fn test_transpile_simple_function() {
1474 let c_code = "int add(int a, int b) { return a + b; }";
1475 let result = transpile(c_code);
1476 assert!(result.is_ok(), "Transpilation should succeed");
1477
1478 let rust_code = result.unwrap();
1479 assert!(rust_code.contains("fn add"), "Should contain function name");
1480 assert!(rust_code.contains("i32"), "Should contain Rust int type");
1481 }
1482
1483 #[test]
1484 fn test_transpile_with_parameters() {
1485 let c_code = "int multiply(int x, int y) { return x * y; }";
1486 let result = transpile(c_code);
1487 assert!(result.is_ok());
1488
1489 let rust_code = result.unwrap();
1490 assert!(rust_code.contains("fn multiply"));
1491 assert!(rust_code.contains("x"));
1492 assert!(rust_code.contains("y"));
1493 }
1494
1495 #[test]
1496 fn test_transpile_void_function() {
1497 let c_code = "void do_nothing() { }";
1498 let result = transpile(c_code);
1499 assert!(result.is_ok());
1500
1501 let rust_code = result.unwrap();
1502 assert!(rust_code.contains("fn do_nothing"));
1503 }
1504
1505 #[test]
1506 fn test_transpile_with_box_transform_simple() {
1507 let c_code = "int get_value() { return 42; }";
1509 let result = transpile_with_box_transform(c_code);
1510 assert!(result.is_ok());
1511
1512 let rust_code = result.unwrap();
1513 assert!(rust_code.contains("fn get_value"));
1514 }
1515
1516 #[test]
1517 fn test_transpile_empty_input() {
1518 let c_code = "";
1519 let result = transpile(c_code);
1520 assert!(result.is_ok());
1522 }
1523
1524 #[test]
1525 fn test_transpile_integration_pipeline() {
1526 let c_code = r#"
1528 int calculate(int a, int b) {
1529 int result;
1530 result = a + b;
1531 return result;
1532 }
1533 "#;
1534 let result = transpile(c_code);
1535 assert!(result.is_ok(), "Full pipeline should execute");
1536
1537 let rust_code = result.unwrap();
1538 assert!(rust_code.contains("fn calculate"));
1539 assert!(rust_code.contains("let mut result"));
1540 }
1541
1542 #[test]
1543 fn test_transpile_with_lifetime_annotations() {
1544 let c_code = "int add(int a, int b) { return a + b; }";
1548 let result = transpile(c_code);
1549 assert!(
1550 result.is_ok(),
1551 "Transpilation with lifetime analysis should succeed"
1552 );
1553
1554 let rust_code = result.unwrap();
1555 assert!(rust_code.contains("fn add"));
1557
1558 }
1561}