Skip to main content

decy_oracle/
context.rs

1//! C-specific decision context
2
3use crate::decisions::CDecisionCategory;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7/// Context for C→Rust transpilation decisions
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CDecisionContext {
10    /// Original C construct (pointer, array, etc.)
11    pub c_construct: CConstruct,
12
13    /// Decision category
14    pub category: CDecisionCategory,
15
16    /// Source location in C
17    pub c_span: Option<SourceSpan>,
18
19    /// Target location in generated Rust
20    pub rust_span: Option<SourceSpan>,
21
22    /// Surrounding C code for pattern matching
23    pub c_context: String,
24
25    /// HIR node hash for deduplication
26    pub hir_hash: u64,
27}
28
29/// Source span (file, line, column)
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct SourceSpan {
32    pub file: String,
33    pub start_line: usize,
34    pub start_col: usize,
35    pub end_line: usize,
36    pub end_col: usize,
37}
38
39impl fmt::Display for SourceSpan {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "{}:{}:{}", self.file, self.start_line, self.start_col)
42    }
43}
44
45impl SourceSpan {
46    /// Create a span for a single line
47    pub fn line(file: impl Into<String>, line: usize) -> Self {
48        Self { file: file.into(), start_line: line, start_col: 1, end_line: line, end_col: 1 }
49    }
50
51    /// Check if this span overlaps with another
52    pub fn overlaps(&self, other: &Self) -> bool {
53        self.file == other.file
54            && self.start_line <= other.end_line
55            && self.end_line >= other.start_line
56    }
57}
58
59/// C language construct being transpiled
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum CConstruct {
62    /// Raw pointer: *T
63    RawPointer { is_const: bool, pointee: String },
64    /// Array: T[N] or T[]
65    Array { element: String, size: Option<usize> },
66    /// String: char* or const char*
67    String { is_const: bool },
68    /// Struct with potential pointer fields
69    Struct { name: String, has_pointers: bool },
70    /// Union
71    Union { name: String },
72    /// Function pointer
73    FunctionPointer { signature: String },
74    /// void*
75    VoidPointer,
76}
77
78impl fmt::Display for CConstruct {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Self::RawPointer { is_const, pointee } => {
82                if *is_const {
83                    write!(f, "const {}*", pointee)
84                } else {
85                    write!(f, "{}*", pointee)
86                }
87            }
88            Self::Array { element, size } => {
89                if let Some(n) = size {
90                    write!(f, "{}[{}]", element, n)
91                } else {
92                    write!(f, "{}[]", element)
93                }
94            }
95            Self::String { is_const } => {
96                if *is_const {
97                    write!(f, "const char*")
98                } else {
99                    write!(f, "char*")
100                }
101            }
102            Self::Struct { name, .. } => write!(f, "struct {}", name),
103            Self::Union { name } => write!(f, "union {}", name),
104            Self::FunctionPointer { signature } => write!(f, "(*)({})", signature),
105            Self::VoidPointer => write!(f, "void*"),
106        }
107    }
108}
109
110/// Lifetime decision for generated Rust code
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum LifetimeDecision {
113    /// Lifetime can be elided
114    Elided,
115    /// Explicit lifetime annotation needed
116    Explicit(String),
117    /// Static lifetime
118    Static,
119    /// Bound to input lifetime
120    InputBound(String),
121}
122
123impl CDecisionContext {
124    /// Create a new context
125    pub fn new(construct: CConstruct, category: CDecisionCategory) -> Self {
126        Self {
127            c_construct: construct,
128            category,
129            c_span: None,
130            rust_span: None,
131            c_context: String::new(),
132            hir_hash: 0,
133        }
134    }
135
136    /// Add C source span
137    pub fn with_c_span(mut self, span: SourceSpan) -> Self {
138        self.c_span = Some(span);
139        self
140    }
141
142    /// Add surrounding context
143    pub fn with_context(mut self, context: impl Into<String>) -> Self {
144        self.c_context = context.into();
145        self
146    }
147
148    /// Convert to context strings for pattern matching
149    pub fn to_context_strings(&self) -> Vec<String> {
150        vec![self.c_construct.to_string(), self.category.to_string(), self.c_context.clone()]
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_c_construct_display() {
160        let ptr = CConstruct::RawPointer { is_const: true, pointee: "int".into() };
161        assert_eq!(ptr.to_string(), "const int*");
162
163        let arr = CConstruct::Array { element: "char".into(), size: Some(256) };
164        assert_eq!(arr.to_string(), "char[256]");
165    }
166
167    #[test]
168    fn test_c_construct_all_variants() {
169        // RawPointer mutable
170        let ptr_mut = CConstruct::RawPointer { is_const: false, pointee: "float".into() };
171        assert_eq!(ptr_mut.to_string(), "float*");
172
173        // Array without size
174        let arr_unsized = CConstruct::Array { element: "int".into(), size: None };
175        assert_eq!(arr_unsized.to_string(), "int[]");
176
177        // String const
178        let str_const = CConstruct::String { is_const: true };
179        assert_eq!(str_const.to_string(), "const char*");
180
181        // String mutable
182        let str_mut = CConstruct::String { is_const: false };
183        assert_eq!(str_mut.to_string(), "char*");
184
185        // Struct
186        let s = CConstruct::Struct { name: "Node".into(), has_pointers: true };
187        assert_eq!(s.to_string(), "struct Node");
188
189        // Union
190        let u = CConstruct::Union { name: "Data".into() };
191        assert_eq!(u.to_string(), "union Data");
192
193        // Function pointer
194        let fp = CConstruct::FunctionPointer { signature: "int, int".into() };
195        assert_eq!(fp.to_string(), "(*)(int, int)");
196
197        // Void pointer
198        let vp = CConstruct::VoidPointer;
199        assert_eq!(vp.to_string(), "void*");
200    }
201
202    #[test]
203    fn test_source_span_overlap() {
204        let span1 = SourceSpan::line("test.c", 10);
205        let span2 = SourceSpan::line("test.c", 10);
206        assert!(span1.overlaps(&span2));
207
208        let span3 = SourceSpan::line("test.c", 20);
209        assert!(!span1.overlaps(&span3));
210
211        let span4 = SourceSpan::line("other.c", 10);
212        assert!(!span1.overlaps(&span4));
213    }
214
215    #[test]
216    fn test_source_span_display() {
217        let span = SourceSpan::line("src/main.c", 42);
218        assert_eq!(span.to_string(), "src/main.c:42:1");
219    }
220
221    #[test]
222    fn test_source_span_multi_line() {
223        let span = SourceSpan {
224            file: "test.c".into(),
225            start_line: 10,
226            start_col: 5,
227            end_line: 15,
228            end_col: 10,
229        };
230
231        let single = SourceSpan::line("test.c", 12);
232        assert!(span.overlaps(&single)); // 12 is within 10-15
233
234        let outside = SourceSpan::line("test.c", 20);
235        assert!(!span.overlaps(&outside));
236    }
237
238    #[test]
239    fn test_context_strings() {
240        let ctx = CDecisionContext::new(
241            CConstruct::RawPointer { is_const: false, pointee: "int".into() },
242            CDecisionCategory::PointerOwnership,
243        )
244        .with_context("function argument");
245
246        let strings = ctx.to_context_strings();
247        assert_eq!(strings.len(), 3);
248        assert!(strings[0].contains("int*"));
249        assert!(strings[1].contains("pointer_ownership"));
250    }
251
252    #[test]
253    fn test_context_with_c_span() {
254        let ctx = CDecisionContext::new(CConstruct::VoidPointer, CDecisionCategory::RawPointerCast)
255            .with_c_span(SourceSpan::line("test.c", 100));
256
257        assert!(ctx.c_span.is_some());
258        assert_eq!(ctx.c_span.as_ref().unwrap().start_line, 100);
259    }
260
261    #[test]
262    fn test_lifetime_decision_variants() {
263        let elided = LifetimeDecision::Elided;
264        assert!(matches!(elided, LifetimeDecision::Elided));
265
266        let explicit = LifetimeDecision::Explicit("'a".into());
267        assert!(matches!(explicit, LifetimeDecision::Explicit(_)));
268
269        let static_lt = LifetimeDecision::Static;
270        assert!(matches!(static_lt, LifetimeDecision::Static));
271
272        let bound = LifetimeDecision::InputBound("'input".into());
273        assert!(matches!(bound, LifetimeDecision::InputBound(_)));
274    }
275
276    #[test]
277    fn test_context_default_values() {
278        let ctx = CDecisionContext::new(CConstruct::VoidPointer, CDecisionCategory::UnsafeBlock);
279
280        assert!(ctx.c_span.is_none());
281        assert!(ctx.rust_span.is_none());
282        assert!(ctx.c_context.is_empty());
283        assert_eq!(ctx.hir_hash, 0);
284    }
285}