Skip to main content

r/interpreter/native/
convert.rs

1//! RValue ↔ SEXP conversion.
2//!
3//! Converts between miniR's `RValue` types and the C-compatible SEXP layout
4//! used by native package code. Handles the type differences:
5//! - miniR Integer is i64, R INTEGER is i32 → truncate with overflow check
6//! - miniR Logical is `Option<bool>`, R LOGICAL is i32 (TRUE=1, FALSE=0, NA=NA_INTEGER)
7//! - miniR uses NullableBuffer bitmaps, R uses sentinel values (NA_REAL, NA_INTEGER)
8
9use super::sexp::{self, Sexp, SexpRec};
10use crate::interpreter::value::*;
11
12// region: RValue → SEXP
13
14/// Convert an RValue to a SEXP for passing to native C code.
15///
16/// The returned SEXP (and any sub-allocations) must be freed after the call.
17/// Caller should track it in the allocation list.
18pub fn rvalue_to_sexp(val: &RValue) -> Sexp {
19    match val {
20        RValue::Null => sexp::mk_null(),
21        RValue::Vector(rv) => {
22            let s = vector_to_sexp(&rv.inner);
23            if let Some(attrs) = &rv.attrs {
24                apply_attrs_to_sexp(s, attrs);
25            }
26            s
27        }
28        RValue::List(list) => list_to_sexp(list),
29        RValue::Environment(env) => env_to_sexp(env),
30        RValue::Language(lang) => language_to_sexp(lang),
31        // Functions, promises can't be meaningfully passed to C.
32        RValue::Function(_) | RValue::Promise(_) => sexp::mk_null(),
33    }
34}
35
36fn vector_to_sexp(vec: &Vector) -> Sexp {
37    match vec {
38        Vector::Double(d) => double_to_sexp(d),
39        Vector::Integer(i) => integer_to_sexp(i),
40        Vector::Logical(l) => logical_to_sexp(l),
41        Vector::Character(c) => character_to_sexp(c),
42        Vector::Raw(r) => raw_to_sexp(r),
43        Vector::Complex(c) => complex_to_sexp(c),
44    }
45}
46
47fn double_to_sexp(d: &Double) -> Sexp {
48    let len = d.len();
49    let s = sexp::alloc_vector(sexp::REALSXP, len as i32);
50    unsafe {
51        let ptr = sexp::real_ptr(s);
52        for i in 0..len {
53            *ptr.add(i) = d.get_opt(i).unwrap_or(sexp::NA_REAL);
54        }
55    }
56    s
57}
58
59fn integer_to_sexp(int: &Integer) -> Sexp {
60    let len = int.len();
61    let s = sexp::alloc_vector(sexp::INTSXP, len as i32);
62    unsafe {
63        let ptr = sexp::integer_ptr(s);
64        for i in 0..len {
65            *ptr.add(i) = match int.get_opt(i) {
66                Some(v) => i32::try_from(v).unwrap_or(sexp::NA_INTEGER),
67                None => sexp::NA_INTEGER,
68            };
69        }
70    }
71    s
72}
73
74fn logical_to_sexp(l: &Logical) -> Sexp {
75    let len = l.len();
76    let s = sexp::alloc_vector(sexp::LGLSXP, len as i32);
77    unsafe {
78        let ptr = sexp::logical_ptr(s);
79        for i in 0..len {
80            *ptr.add(i) = match l[i] {
81                Some(true) => 1,
82                Some(false) => 0,
83                None => sexp::NA_LOGICAL,
84            };
85        }
86    }
87    s
88}
89
90fn character_to_sexp(c: &Character) -> Sexp {
91    let len = c.len();
92    let s = sexp::alloc_vector(sexp::STRSXP, len as i32);
93    unsafe {
94        let elts = (*s).data as *mut Sexp;
95        for i in 0..len {
96            *elts.add(i) = match &c[i] {
97                Some(st) => sexp::mk_char(st),
98                None => sexp::mk_char("NA"), // R_NaString placeholder
99            };
100        }
101    }
102    s
103}
104
105fn complex_to_sexp(c: &ComplexVec) -> Sexp {
106    let len = c.len();
107    let s = sexp::alloc_vector(sexp::CPLXSXP, len as i32);
108    unsafe {
109        // Rcomplex is { double r; double i; } — same layout as num_complex::Complex64
110        let ptr = (*s).data as *mut [f64; 2];
111        for i in 0..len {
112            match c[i] {
113                Some(z) => {
114                    (*ptr.add(i))[0] = z.re;
115                    (*ptr.add(i))[1] = z.im;
116                }
117                None => {
118                    (*ptr.add(i))[0] = sexp::NA_REAL;
119                    (*ptr.add(i))[1] = sexp::NA_REAL;
120                }
121            }
122        }
123    }
124    s
125}
126
127fn raw_to_sexp(r: &[u8]) -> Sexp {
128    let len = r.len();
129    let s = sexp::alloc_vector(sexp::RAWSXP, len as i32);
130    if len > 0 {
131        unsafe {
132            std::ptr::copy_nonoverlapping(r.as_ptr(), (*s).data, len);
133        }
134    }
135    s
136}
137
138fn list_to_sexp(list: &RList) -> Sexp {
139    // Check if this is a wrapped external pointer (has .sexp_ptr attribute)
140    if let Some(attrs) = &list.attrs {
141        if let Some(ptr_val) = attrs.get(".sexp_ptr") {
142            if let Some(addr) = ptr_val.as_vector().and_then(|v| v.as_double_scalar()) {
143                // Return the raw SEXP without copying — it's an external pointer
144                return addr as usize as Sexp;
145            }
146        }
147    }
148
149    let len = list.values.len();
150    let s = sexp::alloc_vector(sexp::VECSXP, len as i32);
151    unsafe {
152        let elts = (*s).data as *mut Sexp;
153        for (i, (_, val)) in list.values.iter().enumerate() {
154            *elts.add(i) = rvalue_to_sexp(val);
155        }
156    }
157
158    // Set names if any element has a name
159    let has_names = list.values.iter().any(|(n, _)| n.is_some());
160    if has_names {
161        let names: Vec<Option<String>> = list.values.iter().map(|(n, _)| n.clone()).collect();
162        let names_sexp = character_to_sexp(&Character(names));
163        super::runtime::Rf_setAttrib(s, unsafe { super::runtime::R_NamesSymbol }, names_sexp);
164    }
165
166    // Apply other attributes (class, etc.)
167    if let Some(attrs) = &list.attrs {
168        apply_attrs_to_sexp(s, attrs);
169    }
170    s
171}
172
173/// Convert a Language (call) to a LANGSXP pairlist.
174///
175/// `Expr::Call { func, args }` becomes `Rf_lcons(func_sexp, Rf_cons(arg1, Rf_cons(arg2, ...)))`.
176/// Other expressions are stashed as sentinel LANGSXPs for Rf_eval round-trips.
177fn language_to_sexp(lang: &crate::interpreter::value::Language) -> Sexp {
178    use crate::parser::ast::Expr;
179
180    match lang.inner.as_ref() {
181        Expr::Call { func, args, .. } => {
182            // Convert function expression to SEXP (usually a symbol)
183            let func_sexp = expr_to_sexp(func);
184            // Build argument pairlist in reverse
185            let mut arg_list = unsafe { super::runtime::R_NilValue };
186            for arg in args.iter().rev() {
187                let val_sexp = match &arg.value {
188                    Some(expr) => expr_to_sexp(expr),
189                    None => unsafe { super::runtime::R_NilValue },
190                };
191                let node = super::runtime::Rf_cons(val_sexp, arg_list);
192                // Set tag if named
193                if let Some(name) = &arg.name {
194                    let tag = super::runtime::Rf_install(
195                        std::ffi::CString::new(name.as_str())
196                            .unwrap_or_default()
197                            .as_ptr(),
198                    );
199                    unsafe {
200                        let pd = (*node).data as *mut sexp::PairlistData;
201                        if !pd.is_null() {
202                            (*pd).tag = tag;
203                        }
204                    }
205                }
206                arg_list = node;
207            }
208            super::runtime::Rf_lcons(func_sexp, arg_list)
209        }
210        // For non-call expressions, stash in the thread-local for Rf_eval round-trips
211        _ => {
212            let idx = super::runtime::stash_rvalue(RValue::Language(lang.clone()));
213            let s = sexp::alloc_vector(sexp::LANGSXP, 0);
214            unsafe {
215                (*s).length = -1; // sentinel
216                (*s).data = idx as *mut u8;
217            }
218            super::runtime::track(s);
219            s
220        }
221    }
222}
223
224/// Convert an Expr to a SEXP for building LANGSXP pairlists.
225fn expr_to_sexp(expr: &crate::parser::ast::Expr) -> Sexp {
226    use crate::parser::ast::Expr;
227    match expr {
228        Expr::Symbol(name) => super::runtime::Rf_install(
229            std::ffi::CString::new(name.as_str())
230                .unwrap_or_default()
231                .as_ptr(),
232        ),
233        Expr::Null => unsafe { super::runtime::R_NilValue },
234        Expr::Bool(true) => super::runtime::Rf_ScalarLogical(1),
235        Expr::Bool(false) => super::runtime::Rf_ScalarLogical(0),
236        Expr::Integer(i) => super::runtime::Rf_ScalarInteger(*i as std::os::raw::c_int),
237        Expr::Double(d) => super::runtime::Rf_ScalarReal(*d),
238        Expr::String(s) => super::runtime::Rf_mkString(
239            std::ffi::CString::new(s.as_str())
240                .unwrap_or_default()
241                .as_ptr(),
242        ),
243        Expr::Call { .. } => {
244            // Nested call — recursively convert
245            let lang = crate::interpreter::value::Language::new(expr.clone());
246            language_to_sexp(&lang)
247        }
248        _ => unsafe { super::runtime::R_NilValue },
249    }
250}
251
252/// Convert an Environment to an ENVSXP.
253///
254/// Stores a cloned Environment handle (Rc) as a leaked Box in the SEXP's data pointer.
255/// C code can pass this opaque SEXP to Rf_findVar, Rf_defineVar, etc. which
256/// extract it via `env_from_sexp`.
257fn env_to_sexp(env: &crate::interpreter::environment::Environment) -> Sexp {
258    let boxed = Box::new(env.clone());
259    let ptr = Box::into_raw(boxed);
260    unsafe {
261        let s = sexp::alloc_vector(sexp::ENVSXP, 0);
262        (*s).data = ptr as *mut u8;
263        s
264    }
265}
266
267/// Extract an Environment from an ENVSXP. Returns None if the SEXP isn't an ENVSXP.
268///
269/// # Safety
270/// `s` must be a valid SEXP pointer.
271pub unsafe fn env_from_sexp(s: Sexp) -> Option<crate::interpreter::environment::Environment> {
272    if s.is_null() {
273        return None;
274    }
275    unsafe {
276        if (*s).stype != sexp::ENVSXP || (*s).data.is_null() {
277            return None;
278        }
279        let env_ptr = (*s).data as *const crate::interpreter::environment::Environment;
280        Some((*env_ptr).clone())
281    }
282}
283
284/// Apply RValue attributes (dim, names, class, dimnames, etc.) to a SEXP.
285fn apply_attrs_to_sexp(s: Sexp, attrs: &indexmap::IndexMap<String, RValue>) {
286    use super::runtime;
287    for (key, val) in attrs {
288        let attr_sexp = rvalue_to_sexp(val);
289        let name_sym = match key.as_str() {
290            "dim" => unsafe { runtime::R_DimSymbol },
291            "names" => unsafe { runtime::R_NamesSymbol },
292            "dimnames" => unsafe { runtime::R_DimNamesSymbol },
293            "class" => unsafe { runtime::R_ClassSymbol },
294            _ => runtime::Rf_install(
295                std::ffi::CString::new(key.as_str())
296                    .unwrap_or_default()
297                    .as_ptr(),
298            ),
299        };
300        runtime::Rf_setAttrib(s, name_sym, attr_sexp);
301    }
302}
303
304// endregion
305
306// region: SEXP → RValue
307
308/// Convert a SEXP result from native C code back to an RValue.
309///
310/// # Safety
311/// `s` must be a valid SEXP pointer allocated by our runtime or by C code
312/// using our allocVector.
313pub unsafe fn sexp_to_rvalue(s: Sexp) -> RValue {
314    if s.is_null() {
315        return RValue::Null;
316    }
317    let rec: &SexpRec = &*s;
318    let mut result = match rec.stype {
319        sexp::NILSXP => return RValue::Null,
320        sexp::REALSXP => sexp_real_to_rvalue(rec),
321        sexp::INTSXP => sexp_int_to_rvalue(rec),
322        sexp::LGLSXP => sexp_lgl_to_rvalue(rec),
323        sexp::STRSXP => sexp_str_to_rvalue(rec),
324        sexp::VECSXP => sexp_vec_to_rvalue(rec),
325        sexp::RAWSXP => sexp_raw_to_rvalue(rec),
326        sexp::CPLXSXP => sexp_complex_to_rvalue(rec),
327        sexp::CHARSXP => {
328            let st = sexp::char_data(s);
329            RValue::vec(Vector::Character(vec![Some(st.to_string())].into()))
330        }
331        sexp::ENVSXP => {
332            if let Some(env) = env_from_sexp(s) {
333                return RValue::Environment(env);
334            }
335            return RValue::Null;
336        }
337        // External pointer — wrap as a List with a ".sexp_ptr" attribute
338        // storing the raw address as an integer. This allows round-trips
339        // through .Call without adding a new RValue variant.
340        22 => {
341            // EXTPTRSXP = 22
342            let addr = s as usize;
343            let mut list = RList::new(vec![]);
344            let mut attrs = indexmap::IndexMap::new();
345            attrs.insert(
346                ".sexp_ptr".to_string(),
347                RValue::vec(Vector::Double(vec![Some(addr as f64)].into())),
348            );
349            attrs.insert(
350                "class".to_string(),
351                RValue::vec(Vector::Character(
352                    vec![Some("externalptr".to_string())].into(),
353                )),
354            );
355            list.attrs = Some(Box::new(attrs));
356            return RValue::List(list);
357        }
358        _ => return RValue::Null,
359    };
360
361    // Read attributes from the SEXP attrib pairlist
362    if !rec.attrib.is_null() {
363        read_sexp_attrs(rec.attrib, &mut result);
364    }
365
366    result
367}
368
369/// Read attributes from a SEXP pairlist (LISTSXP chain) and apply to an RValue.
370unsafe fn read_sexp_attrs(mut attr: Sexp, result: &mut RValue) {
371    // Walk the pairlist: each node has TAG (name symbol), CAR (value), CDR (next)
372    while !attr.is_null() && (*attr).stype == sexp::LISTSXP {
373        let pairlist_data = (*attr).data as *const sexp::PairlistData;
374        if pairlist_data.is_null() {
375            break;
376        }
377        let tag = (*pairlist_data).tag;
378        let car = (*pairlist_data).car;
379        let cdr = (*pairlist_data).cdr;
380
381        // Read attribute name from the tag symbol
382        if !tag.is_null() && (*tag).stype == sexp::SYMSXP && !(*tag).data.is_null() {
383            let name = sexp::char_data(tag); // SYMSXP stores name like CHARSXP
384            let value = sexp_to_rvalue(car);
385
386            match result {
387                RValue::Vector(rv) => {
388                    rv.set_attr(name.to_string(), value);
389                }
390                RValue::List(list) => {
391                    list.attrs
392                        .get_or_insert_with(|| Box::new(indexmap::IndexMap::new()))
393                        .insert(name.to_string(), value);
394                }
395                _ => {}
396            }
397        }
398
399        attr = cdr;
400    }
401}
402
403unsafe fn sexp_real_to_rvalue(rec: &SexpRec) -> RValue {
404    let len = rec.length.max(0) as usize;
405    let ptr = rec.data as *const f64;
406    let mut vals = Vec::with_capacity(len);
407    for i in 0..len {
408        let v = *ptr.add(i);
409        if sexp::is_na_real(v) {
410            vals.push(None);
411        } else {
412            vals.push(Some(v));
413        }
414    }
415    RValue::vec(Vector::Double(vals.into()))
416}
417
418unsafe fn sexp_int_to_rvalue(rec: &SexpRec) -> RValue {
419    let len = rec.length.max(0) as usize;
420    let ptr = rec.data as *const i32;
421    let mut vals = Vec::with_capacity(len);
422    for i in 0..len {
423        let v = *ptr.add(i);
424        if v == sexp::NA_INTEGER {
425            vals.push(None);
426        } else {
427            vals.push(Some(i64::from(v)));
428        }
429    }
430    RValue::vec(Vector::Integer(vals.into()))
431}
432
433unsafe fn sexp_lgl_to_rvalue(rec: &SexpRec) -> RValue {
434    let len = rec.length.max(0) as usize;
435    let ptr = rec.data as *const i32;
436    let mut vals = Vec::with_capacity(len);
437    for i in 0..len {
438        let v = *ptr.add(i);
439        if v == sexp::NA_LOGICAL {
440            vals.push(None);
441        } else {
442            vals.push(Some(v != 0));
443        }
444    }
445    RValue::vec(Vector::Logical(vals.into()))
446}
447
448unsafe fn sexp_str_to_rvalue(rec: &SexpRec) -> RValue {
449    let len = rec.length.max(0) as usize;
450    let elts = rec.data as *const Sexp;
451    let mut vals = Vec::with_capacity(len);
452    for i in 0..len {
453        let elt = *elts.add(i);
454        if elt.is_null() {
455            vals.push(None);
456        } else {
457            vals.push(Some(sexp::char_data(elt).to_string()));
458        }
459    }
460    RValue::vec(Vector::Character(vals.into()))
461}
462
463unsafe fn sexp_vec_to_rvalue(rec: &SexpRec) -> RValue {
464    let len = rec.length.max(0) as usize;
465    let elts = rec.data as *const Sexp;
466    let mut vals = Vec::with_capacity(len);
467    for i in 0..len {
468        let elt = *elts.add(i);
469        vals.push((None, sexp_to_rvalue(elt)));
470    }
471    RValue::List(RList::new(vals))
472}
473
474unsafe fn sexp_raw_to_rvalue(rec: &SexpRec) -> RValue {
475    let len = rec.length.max(0) as usize;
476    let mut buf = vec![0u8; len];
477    if len > 0 {
478        std::ptr::copy_nonoverlapping(rec.data, buf.as_mut_ptr(), len);
479    }
480    RValue::vec(Vector::Raw(buf))
481}
482
483unsafe fn sexp_complex_to_rvalue(rec: &SexpRec) -> RValue {
484    let len = rec.length.max(0) as usize;
485    let ptr = rec.data as *const [f64; 2]; // Rcomplex = { double r, i }
486    let mut vals = Vec::with_capacity(len);
487    for i in 0..len {
488        let pair = &*ptr.add(i);
489        if sexp::is_na_real(pair[0]) {
490            vals.push(None);
491        } else {
492            vals.push(Some(num_complex::Complex64::new(pair[0], pair[1])));
493        }
494    }
495    RValue::vec(Vector::Complex(vals.into()))
496}
497
498// endregion