1#![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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct TranspiledFile {
49 pub source_path: PathBuf,
51
52 pub rust_code: String,
54
55 pub dependencies: Vec<PathBuf>,
57
58 pub functions_exported: Vec<String>,
60
61 pub ffi_declarations: String,
63}
64
65impl TranspiledFile {
66 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#[derive(Debug, Clone, Default)]
83pub struct ProjectContext {
84 types: HashMap<String, String>,
86
87 functions: HashMap<String, String>,
89
90 transpiled_files: HashMap<PathBuf, TranspiledFile>,
92}
93
94impl ProjectContext {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn add_transpiled_file(&mut self, file: &TranspiledFile) {
105 contract_pre_configuration!();
106 self.transpiled_files.insert(file.source_path.clone(), file.clone());
108
109 if file.rust_code.contains("struct") {
112 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 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 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 { graph: DiGraph::new(), path_to_node: HashMap::new() }
175 }
176
177 pub fn is_empty(&self) -> bool {
179 self.graph.node_count() == 0
180 }
181
182 pub fn file_count(&self) -> usize {
184 self.graph.node_count()
185 }
186
187 pub fn contains_file(&self, path: &Path) -> bool {
189 self.path_to_node.contains_key(path)
190 }
191
192 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 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 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 pub fn topological_sort(&self) -> Result<Vec<PathBuf>> {
229 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 build_order.reverse();
245
246 Ok(build_order)
247 }
248
249 pub fn from_files(files: &[PathBuf]) -> Result<Self> {
253 let mut graph = Self::new();
254
255 for file in files {
257 graph.add_file(file);
258 }
259
260 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 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 if graph.contains_file(&include_path) {
275 graph.add_dependency(file, &include_path);
276 }
277 }
278 }
279
280 Ok(graph)
281 }
282
283 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 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 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#[derive(Debug, Clone)]
332pub struct CacheStatistics {
333 pub hits: usize,
335 pub misses: usize,
337 pub total_files: usize,
339}
340
341#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
343struct CacheEntry {
344 hash: String,
346 transpiled: TranspiledFile,
348 dependency_hashes: HashMap<PathBuf, String>,
350}
351
352#[derive(Debug, Clone)]
378pub struct TranspilationCache {
379 entries: HashMap<PathBuf, CacheEntry>,
381 cache_dir: Option<PathBuf>,
383 hits: usize,
385 misses: usize,
386}
387
388impl TranspilationCache {
389 pub fn new() -> Self {
391 Self { entries: HashMap::new(), cache_dir: None, hits: 0, misses: 0 }
392 }
393
394 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 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 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, };
427
428 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 pub fn get(&mut self, path: &Path) -> Option<&TranspiledFile> {
448 let entry = self.entries.get(&path.to_path_buf())?;
449
450 let current_hash = self.compute_hash(path).ok()?;
452 if current_hash != entry.hash {
453 self.misses += 1;
454 return None;
455 }
456
457 for (dep_path, cached_hash) in &entry.dependency_hashes {
459 if let Ok(current_dep_hash) = self.compute_hash(dep_path) {
460 if ¤t_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 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 pub fn load(cache_dir: &Path) -> Result<Self> {
492 let cache_file = cache_dir.join("cache.json");
493
494 if !cache_file.exists() {
495 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 pub fn clear(&mut self) {
510 self.entries.clear();
511 self.hits = 0;
512 self.misses = 0;
513 }
514
515 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
527fn 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 if trimmed.starts_with("#include") {
562 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 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 result.push_str(line);
580 result.push('\n');
581 continue;
582 }
583 } else {
584 result.push_str(line);
586 result.push('\n');
587 continue;
588 };
589
590 if is_system {
593 result.push_str(&format!("// {}\n", line));
595
596 if !injected_headers.contains(filename) {
598 injected_headers.insert(filename.to_string());
600
601 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 result.push_str(&format!("// Unknown system header: {}\n", filename));
610 }
611 }
612
613 continue;
614 }
615
616 let include_path = base_dir.join(filename);
618
619 if processed.contains(&include_path) {
621 result.push_str(&format!("// Already included: {}\n", filename));
623 continue;
624 }
625
626 if let Ok(included_content) = std::fs::read_to_string(&include_path) {
628 processed.insert(include_path.clone());
630
631 let included_dir = include_path.parent().unwrap_or(base_dir);
633
634 let preprocessed = preprocess_includes(
636 &included_content,
637 Some(included_dir),
638 processed,
639 stdlib_prototypes,
640 injected_headers,
641 )?;
642
643 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 anyhow::bail!("Failed to find include file: {}", include_path.display());
650 }
651 } else {
652 result.push_str(line);
654 result.push('\n');
655 }
656 }
657
658 Ok(result)
659}
660
661pub fn transpile(c_code: &str) -> Result<String> {
687 contract_pre_configuration!();
688 transpile_with_includes(c_code, None)
689}
690
691pub 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 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 let rust_code = transpile(c_code)?;
736
737 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
751pub 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 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 = ¶ms[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
1046pub fn transpile_with_includes(c_code: &str, base_dir: Option<&Path>) -> Result<String> {
1064 contract_pre_configuration!();
1065 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 let parser = CParser::new().context("Failed to create C parser")?;
1083 let ast = parser.parse(&preprocessed).context("Failed to parse C code")?;
1084
1085 let all_hir_functions: Vec<HirFunction> =
1087 ast.functions().iter().map(HirFunction::from_ast_function).collect();
1088
1089 let hir_functions = deduplicate_functions(all_hir_functions);
1093
1094 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 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 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 if v.is_extern() && v.initializer().is_none() {
1142 return false;
1143 }
1144 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 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 let slice_func_args = build_slice_func_arg_mappings(&hir_functions);
1172
1173 let transformed_functions: Vec<_> = hir_functions
1175 .into_iter()
1176 .map(|func| transform_function_with_ownership(func))
1177 .collect();
1178
1179 let code_generator = CodeGenerator::new();
1181 let mut rust_code = String::new();
1182
1183 let mut emitted_structs = std::collections::HashSet::new();
1185 let mut emitted_typedefs = std::collections::HashSet::new();
1186
1187 for hir_struct in &hir_structs {
1189 let struct_name = hir_struct.name();
1190 if emitted_structs.contains(struct_name) {
1191 continue; }
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 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 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 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 rust_code.push_str("static mut ERRNO: i32 = 0;\n");
1235
1236 for typedef in &hir_typedefs {
1238 let typedef_name = typedef.name();
1239 if emitted_typedefs.contains(typedef_name) {
1240 continue; }
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 let global_vars = generate_global_variable_code(
1252 &hir_variables,
1253 &hir_structs,
1254 &code_generator,
1255 &mut rust_code,
1256 );
1257
1258 let all_function_sigs = build_all_function_sigs(&transformed_functions);
1260
1261 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 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
1294pub fn transpile_from_file_path(file_path: &Path) -> Result<String> {
1305 contract_pre_configuration!();
1306 let c_code = std::fs::read_to_string(file_path)
1308 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
1309
1310 transpile_with_file(&c_code, file_path)
1312}
1313
1314fn transpile_with_file(c_code: &str, file_path: &Path) -> Result<String> {
1317 let base_dir = file_path.parent();
1318
1319 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 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 process_ast_to_rust(ast, base_dir)
1337}
1338
1339fn process_ast_to_rust(ast: decy_parser::Ast, _base_dir: Option<&Path>) -> Result<String> {
1341 let all_hir_functions: Vec<HirFunction> =
1343 ast.functions().iter().map(HirFunction::from_ast_function).collect();
1344
1345 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 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 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 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 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 let code_generator = CodeGenerator::new();
1432 let mut rust_code = String::new();
1433
1434 let mut emitted_structs = std::collections::HashSet::new();
1436 let mut emitted_typedefs = std::collections::HashSet::new();
1437
1438 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 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 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 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 rust_code.push_str("static mut ERRNO: i32 = 0;\n");
1485
1486 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 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_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_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 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
1552pub fn transpile_with_box_transform(c_code: &str) -> Result<String> {
1574 contract_pre_configuration!();
1575 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 let hir_functions: Vec<HirFunction> =
1581 ast.functions().iter().map(HirFunction::from_ast_function).collect();
1582
1583 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 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
1600pub fn transpile_file(path: &Path, _context: &ProjectContext) -> Result<TranspiledFile> {
1627 contract_pre_configuration!();
1628 let c_code = std::fs::read_to_string(path)
1630 .with_context(|| format!("Failed to read file: {}", path.display()))?;
1631
1632 let dependencies = extract_dependencies(path, &c_code)?;
1634
1635 let rust_code = transpile(&c_code)?;
1637
1638 let functions_exported = extract_function_names(&rust_code);
1640
1641 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
1653fn 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 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
1682fn 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 if (trimmed.starts_with("fn ") || trimmed.starts_with("pub fn ")) && trimmed.contains('(') {
1692 let start_idx = if trimmed.starts_with("pub fn ") {
1693 7 } else {
1695 3 };
1697
1698 if let Some(paren_idx) = trimmed[start_idx..].find('(') {
1699 let func_name = &trimmed[start_idx..start_idx + paren_idx];
1700 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
1714fn 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
1734fn 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
1746fn 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
1760fn statement_compares_to_null(stmt: &HirStatement, var_name: &str) -> bool {
1762 match stmt {
1763 HirStatement::If { condition, then_block, else_block } => {
1764 if expression_compares_to_null(condition, var_name) {
1766 return true;
1767 }
1768 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
1792fn 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 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 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 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
1827fn statement_uses_pointer_arithmetic(stmt: &HirStatement, var_name: &str) -> bool {
1829 use decy_hir::BinaryOperator;
1830 match stmt {
1831 HirStatement::Assignment { target, value } => {
1832 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;