Skip to main content

r/interpreter/native/
runtime.rs

1//! Rust-native R C API runtime.
2//!
3//! These functions deref raw SEXP pointers from C code — this is inherently
4//! unsafe but required for C API compatibility. We suppress the clippy lint
5//! at module level since every function in this module works with raw pointers.
6#![allow(clippy::not_unsafe_ptr_arg_deref)]
7//!
8//! Implements the R C API functions (`Rf_allocVector`, `Rf_protect`, etc.)
9//! as `extern "C"` Rust functions that are compiled into the miniR binary.
10//! Package `.so` files resolve these symbols at load time.
11//!
12//! setjmp/longjmp-based functions (`Rf_error`, `_minir_call_protected`)
13//! are in `csrc/native_trampoline.c` (compiled via build.rs) because
14//! longjmp is not safely callable from Rust.
15
16use std::ffi::CStr;
17use std::os::raw::{c_char, c_int, c_void};
18use std::ptr;
19
20use super::sexp::{self, PairlistData, Sexp, SexpRec};
21
22// region: allocation tracking
23
24/// Linked list node for tracking allocations.
25#[allow(dead_code)] // sexp field read by R_PreserveObject; free_allocs currently leaks
26struct AllocNode {
27    sexp: Sexp,
28    next: *mut AllocNode,
29}
30
31/// Result type for interpreter callbacks.
32type CbResult = Result<crate::interpreter::value::RValue, crate::interpreter::value::RError>;
33
34/// Callback function pointers set by the Rust interpreter before each .Call.
35#[derive(Default)]
36pub struct InterpreterCallbacks {
37    pub find_var: Option<fn(&str) -> Option<crate::interpreter::value::RValue>>,
38    pub define_var: Option<fn(&str, crate::interpreter::value::RValue)>,
39    pub eval_expr: Option<fn(&crate::interpreter::value::RValue) -> CbResult>,
40    pub parse_text: Option<fn(&str) -> CbResult>,
41}
42
43/// Thread-local allocation state for the current .Call invocation.
44/// This is in the binary (shared by all packages), not per-.so.
45struct RuntimeState {
46    alloc_head: *mut AllocNode,
47    protect_stack: Vec<Sexp>,
48    callbacks: InterpreterCallbacks,
49    /// Stash for parsed RValues that need to survive the SEXP round-trip.
50    /// R_ParseVector stores parsed Language values here; Rf_eval retrieves them.
51    /// LANGSXP nodes created by R_ParseVector carry a stash index in their data pointer.
52    rvalue_stash: Vec<crate::interpreter::value::RValue>,
53}
54
55thread_local! {
56    static STATE: std::cell::RefCell<RuntimeState> = std::cell::RefCell::new(RuntimeState {
57        alloc_head: ptr::null_mut(),
58        protect_stack: Vec::with_capacity(128),
59        callbacks: InterpreterCallbacks::default(),
60        rvalue_stash: Vec::new(),
61    });
62}
63
64/// Stash an RValue and return its index. Used by R_ParseVector.
65pub(super) fn stash_rvalue(val: crate::interpreter::value::RValue) -> usize {
66    STATE.with(|state| {
67        let mut st = state.borrow_mut();
68        let idx = st.rvalue_stash.len();
69        st.rvalue_stash.push(val);
70        idx
71    })
72}
73
74/// Retrieve a stashed RValue by index. Used by Rf_eval.
75fn get_stashed_rvalue(idx: usize) -> Option<crate::interpreter::value::RValue> {
76    STATE.with(|state| {
77        let st = state.borrow();
78        st.rvalue_stash.get(idx).cloned()
79    })
80}
81
82/// Set interpreter callbacks for the current .Call invocation.
83pub fn set_callbacks(callbacks: InterpreterCallbacks) {
84    STATE.with(|state| {
85        state.borrow_mut().callbacks = callbacks;
86    });
87}
88
89/// Initialize the global R_BaseEnv, R_GlobalEnv, R_EmptyEnv SEXPs from interpreter envs.
90/// Called once from the package loader before the first .Call.
91pub fn init_global_envs(
92    base_env: &crate::interpreter::environment::Environment,
93    global_env: &crate::interpreter::environment::Environment,
94) {
95    use super::convert::rvalue_to_sexp;
96    use crate::interpreter::value::RValue;
97    unsafe {
98        R_BaseEnv = rvalue_to_sexp(&RValue::Environment(base_env.clone()));
99        R_GlobalEnv = rvalue_to_sexp(&RValue::Environment(global_env.clone()));
100        R_BaseNamespace = R_BaseEnv;
101        // R_EmptyEnv stays NULL — it represents an empty env with no parent
102    }
103}
104
105/// Decompile a pairlist-style LANGSXP into R source text.
106///
107/// Example: `Rf_lang3(Rf_install("::"), Rf_install("base"), Rf_install("stop"))`
108/// becomes `base::stop` (infix). Regular calls become `abort(message = "...")`.
109fn langsxp_to_text(s: Sexp) -> Option<String> {
110    if s.is_null() || unsafe { (*s).stype != sexp::LANGSXP } {
111        return None;
112    }
113
114    let data = unsafe { (*s).data as *const sexp::PairlistData };
115    if data.is_null() {
116        return None;
117    }
118
119    // CAR = function (SYMSXP), CDR = argument chain
120    let func = unsafe { (*data).car };
121    let args = unsafe { (*data).cdr };
122
123    // Get function name from SYMSXP
124    let func_name = if !func.is_null() && unsafe { (*func).stype } == sexp::SYMSXP {
125        unsafe { sexp::char_data(func) }.to_string()
126    } else if !func.is_null() && unsafe { (*func).stype } == sexp::LANGSXP {
127        // Nested call — recursively decompile (e.g. base::stop(...))
128        langsxp_to_text(func)?
129    } else {
130        return None;
131    };
132
133    // Collect arguments from pairlist chain
134    let mut arg_strs = Vec::new();
135    let mut node = args;
136    while !node.is_null() && unsafe { (*node).stype } != sexp::NILSXP {
137        let nd = unsafe { (*node).data as *const sexp::PairlistData };
138        if nd.is_null() {
139            break;
140        }
141
142        let val = unsafe { (*nd).car };
143        let tag = unsafe { (*nd).tag };
144
145        // Format the argument value
146        let val_str = sexp_to_text_repr(val);
147
148        // If there's a tag (named argument), include it
149        if !tag.is_null() && unsafe { (*tag).stype } == sexp::SYMSXP {
150            let tag_name = unsafe { sexp::char_data(tag) };
151            if !tag_name.is_empty() {
152                arg_strs.push(format!("{tag_name} = {val_str}"));
153            } else {
154                arg_strs.push(val_str);
155            }
156        } else {
157            arg_strs.push(val_str);
158        }
159
160        node = unsafe { (*nd).cdr };
161    }
162
163    // Handle infix operators like `::`, `$`
164    if (func_name == "::" || func_name == ":::" || func_name == "$") && arg_strs.len() == 2 {
165        return Some(format!("{}{func_name}{}", arg_strs[0], arg_strs[1]));
166    }
167
168    Some(format!("{}({})", func_name, arg_strs.join(", ")))
169}
170
171/// Convert a SEXP value to its text representation for decompilation.
172fn sexp_to_text_repr(s: Sexp) -> String {
173    if s.is_null() {
174        return "NULL".to_string();
175    }
176    let stype = unsafe { (*s).stype };
177    match stype {
178        sexp::NILSXP => "NULL".to_string(),
179        sexp::SYMSXP => unsafe { sexp::char_data(s) }.to_string(),
180        sexp::LANGSXP => langsxp_to_text(s).unwrap_or_else(|| "NULL".to_string()),
181        sexp::LGLSXP => {
182            let rval = unsafe { super::convert::sexp_to_rvalue(s) };
183            format!("{}", rval)
184        }
185        sexp::INTSXP | sexp::REALSXP => {
186            let rval = unsafe { super::convert::sexp_to_rvalue(s) };
187            format!("{}", rval)
188        }
189        sexp::STRSXP => {
190            let rval = unsafe { super::convert::sexp_to_rvalue(s) };
191            if let Some(rv) = rval.as_vector() {
192                if let Some(s) = rv.as_character_scalar() {
193                    return format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""));
194                }
195            }
196            format!("{}", rval)
197        }
198        _ => {
199            // For unknown types, try converting to RValue
200            let rval = unsafe { super::convert::sexp_to_rvalue(s) };
201            format!("{}", rval)
202        }
203    }
204}
205
206/// Clear interpreter callbacks after .Call returns.
207pub fn clear_callbacks() {
208    STATE.with(|state| {
209        let mut st = state.borrow_mut();
210        st.callbacks = InterpreterCallbacks::default();
211        st.rvalue_stash.clear();
212    });
213}
214
215pub(super) fn track(s: Sexp) {
216    let node = Box::into_raw(Box::new(AllocNode {
217        sexp: s,
218        next: ptr::null_mut(),
219    }));
220    STATE.with(|state| {
221        let mut st = state.borrow_mut();
222        unsafe {
223            (*node).next = st.alloc_head;
224        }
225        st.alloc_head = node;
226    });
227}
228
229// endregion
230
231// region: sentinel globals
232
233// Safety: These globals are initialized once by `init_globals()` and never
234// written again. Multiple reader threads are safe. The `static mut` is used
235// because `#[no_mangle]` extern statics must be `static mut` for C ABI compat.
236// The `unsafe` blocks in init_globals are the only writes.
237
238static mut NIL_REC: SexpRec = SexpRec {
239    stype: sexp::NILSXP,
240    flags: 0,
241    padding: 0,
242    length: 0,
243    data: ptr::null_mut(),
244    attrib: ptr::null_mut(),
245};
246
247/// R_NilValue — exported to C code.
248#[no_mangle]
249pub static mut R_NilValue: Sexp = ptr::null_mut();
250
251#[no_mangle]
252pub static mut R_NaString: Sexp = ptr::null_mut();
253
254/// R_NaInt — addressable NA_INTEGER constant for C code that takes &NA_INTEGER.
255#[no_mangle]
256pub static mut R_NaInt: c_int = i32::MIN;
257
258/// R_NaReal — addressable NA_REAL constant for C code that takes &NA_REAL.
259#[no_mangle]
260pub static mut R_NaReal: f64 = sexp::NA_REAL;
261
262#[no_mangle]
263pub static mut R_BlankString: Sexp = ptr::null_mut();
264
265#[no_mangle]
266pub static mut R_GlobalEnv: Sexp = ptr::null_mut();
267
268#[no_mangle]
269pub static mut R_BaseEnv: Sexp = ptr::null_mut();
270
271#[no_mangle]
272pub static mut R_UnboundValue: Sexp = ptr::null_mut();
273
274// Well-known symbols
275static mut SYM_NAMES: SexpRec = SexpRec {
276    stype: sexp::SYMSXP,
277    flags: 0,
278    padding: 0,
279    length: 5,
280    data: ptr::null_mut(),
281    attrib: ptr::null_mut(),
282};
283static mut SYM_DIM: SexpRec = SexpRec {
284    stype: sexp::SYMSXP,
285    flags: 0,
286    padding: 0,
287    length: 3,
288    data: ptr::null_mut(),
289    attrib: ptr::null_mut(),
290};
291static mut SYM_DIMNAMES: SexpRec = SexpRec {
292    stype: sexp::SYMSXP,
293    flags: 0,
294    padding: 0,
295    length: 8,
296    data: ptr::null_mut(),
297    attrib: ptr::null_mut(),
298};
299static mut SYM_CLASS: SexpRec = SexpRec {
300    stype: sexp::SYMSXP,
301    flags: 0,
302    padding: 0,
303    length: 5,
304    data: ptr::null_mut(),
305    attrib: ptr::null_mut(),
306};
307static mut SYM_ROWNAMES: SexpRec = SexpRec {
308    stype: sexp::SYMSXP,
309    flags: 0,
310    padding: 0,
311    length: 10,
312    data: ptr::null_mut(),
313    attrib: ptr::null_mut(),
314};
315static mut SYM_LEVELS: SexpRec = SexpRec {
316    stype: sexp::SYMSXP,
317    flags: 0,
318    padding: 0,
319    length: 6,
320    data: ptr::null_mut(),
321    attrib: ptr::null_mut(),
322};
323
324#[no_mangle]
325pub static mut R_NamesSymbol: Sexp = ptr::null_mut();
326#[no_mangle]
327pub static mut R_DimSymbol: Sexp = ptr::null_mut();
328#[no_mangle]
329pub static mut R_DimNamesSymbol: Sexp = ptr::null_mut();
330#[no_mangle]
331pub static mut R_ClassSymbol: Sexp = ptr::null_mut();
332#[no_mangle]
333pub static mut R_RowNamesSymbol: Sexp = ptr::null_mut();
334#[no_mangle]
335pub static mut R_LevelsSymbol: Sexp = ptr::null_mut();
336
337static mut SYM_DOTS: SexpRec = SexpRec {
338    stype: sexp::SYMSXP,
339    flags: 0,
340    padding: 0,
341    length: 3,
342    data: ptr::null_mut(),
343    attrib: ptr::null_mut(),
344};
345#[no_mangle]
346pub static mut R_DotsSymbol: Sexp = ptr::null_mut();
347
348#[no_mangle]
349pub static mut _minir_current_dll_info: *mut c_void = ptr::null_mut();
350
351/// Initialize global sentinels. Called once at interpreter startup.
352pub fn init_globals() {
353    unsafe {
354        // Static string data for sentinels
355        static NA_STR: &[u8] = b"NA\0";
356        static BLANK_STR: &[u8] = b"\0";
357        static NAMES_STR: &[u8] = b"names\0";
358        static DIM_STR: &[u8] = b"dim\0";
359        static DIMNAMES_STR: &[u8] = b"dimnames\0";
360        static CLASS_STR: &[u8] = b"class\0";
361        static ROWNAMES_STR: &[u8] = b"row.names\0";
362        static LEVELS_STR: &[u8] = b"levels\0";
363        static DOTS_STR: &[u8] = b"...\0";
364
365        R_NilValue = &raw mut NIL_REC;
366
367        // R_NaString
368        static mut NA_STRING_REC: SexpRec = SexpRec {
369            stype: sexp::CHARSXP,
370            flags: 0,
371            padding: 0,
372            length: 2,
373            data: ptr::null_mut(),
374            attrib: ptr::null_mut(),
375        };
376        NA_STRING_REC.data = NA_STR.as_ptr() as *mut u8;
377        NA_STRING_REC.attrib = R_NilValue;
378        R_NaString = &raw mut NA_STRING_REC;
379
380        // R_BlankString
381        static mut BLANK_STRING_REC: SexpRec = SexpRec {
382            stype: sexp::CHARSXP,
383            flags: 0,
384            padding: 0,
385            length: 0,
386            data: ptr::null_mut(),
387            attrib: ptr::null_mut(),
388        };
389        BLANK_STRING_REC.data = BLANK_STR.as_ptr() as *mut u8;
390        BLANK_STRING_REC.attrib = R_NilValue;
391        R_BlankString = &raw mut BLANK_STRING_REC;
392
393        R_GlobalEnv = R_NilValue;
394        R_BaseEnv = R_NilValue;
395        R_UnboundValue = R_NilValue;
396        R_EmptyEnv = R_NilValue;
397        R_MissingArg = R_NilValue;
398        R_NamespaceRegistry = R_NilValue;
399        R_Srcref = R_NilValue;
400        R_BaseNamespace = R_NilValue;
401        R_NameSymbol = R_NilValue;
402        R_BraceSymbol = R_NilValue;
403        R_BracketSymbol = R_NilValue;
404        R_Bracket2Symbol = R_NilValue;
405        R_DollarSymbol = R_NilValue;
406        R_DoubleColonSymbol = R_NilValue;
407        R_TripleColonSymbol = R_NilValue;
408
409        // Symbol sentinels
410        SYM_NAMES.data = NAMES_STR.as_ptr() as *mut u8;
411        SYM_NAMES.attrib = R_NilValue;
412        R_NamesSymbol = &raw mut SYM_NAMES;
413
414        SYM_DIM.data = DIM_STR.as_ptr() as *mut u8;
415        SYM_DIM.attrib = R_NilValue;
416        R_DimSymbol = &raw mut SYM_DIM;
417
418        SYM_DIMNAMES.data = DIMNAMES_STR.as_ptr() as *mut u8;
419        SYM_DIMNAMES.attrib = R_NilValue;
420        R_DimNamesSymbol = &raw mut SYM_DIMNAMES;
421
422        SYM_CLASS.data = CLASS_STR.as_ptr() as *mut u8;
423        SYM_CLASS.attrib = R_NilValue;
424        R_ClassSymbol = &raw mut SYM_CLASS;
425
426        SYM_ROWNAMES.data = ROWNAMES_STR.as_ptr() as *mut u8;
427        SYM_ROWNAMES.attrib = R_NilValue;
428        R_RowNamesSymbol = &raw mut SYM_ROWNAMES;
429
430        SYM_LEVELS.data = LEVELS_STR.as_ptr() as *mut u8;
431        SYM_LEVELS.attrib = R_NilValue;
432        R_LevelsSymbol = &raw mut SYM_LEVELS;
433
434        SYM_DOTS.data = DOTS_STR.as_ptr() as *mut u8;
435        SYM_DOTS.attrib = R_NilValue;
436        R_DotsSymbol = &raw mut SYM_DOTS;
437    }
438}
439
440// endregion
441
442// region: C allocator wrappers
443
444extern "C" {
445    fn calloc(count: usize, size: usize) -> *mut u8;
446    fn realloc(ptr: *mut u8, size: usize) -> *mut u8;
447    fn free(ptr: *mut u8);
448}
449
450// endregion
451
452// region: Rf_allocVector
453
454#[no_mangle]
455pub extern "C" fn Rf_allocVector(stype: c_int, length: isize) -> Sexp {
456    let s = sexp::alloc_vector(stype as u8, length as i32);
457    if !s.is_null() {
458        unsafe {
459            (*s).attrib = R_NilValue;
460        }
461    }
462    track(s);
463    s
464}
465
466#[no_mangle]
467pub extern "C" fn Rf_allocMatrix(stype: c_int, nrow: c_int, ncol: c_int) -> Sexp {
468    Rf_allocVector(stype, (nrow as isize) * (ncol as isize))
469}
470
471// endregion
472
473// region: Scalar constructors
474
475#[no_mangle]
476pub extern "C" fn Rf_ScalarReal(x: f64) -> Sexp {
477    let s = Rf_allocVector(sexp::REALSXP as c_int, 1);
478    unsafe {
479        *((*s).data as *mut f64) = x;
480    }
481    s
482}
483
484#[no_mangle]
485pub extern "C" fn Rf_ScalarInteger(x: c_int) -> Sexp {
486    let s = Rf_allocVector(sexp::INTSXP as c_int, 1);
487    unsafe {
488        *((*s).data as *mut i32) = x;
489    }
490    s
491}
492
493#[no_mangle]
494pub extern "C" fn Rf_ScalarLogical(x: c_int) -> Sexp {
495    let s = Rf_allocVector(sexp::LGLSXP as c_int, 1);
496    unsafe {
497        *((*s).data as *mut i32) = x;
498    }
499    s
500}
501
502#[no_mangle]
503pub extern "C" fn Rf_ScalarString(x: Sexp) -> Sexp {
504    let s = Rf_allocVector(sexp::STRSXP as c_int, 1);
505    unsafe {
506        let elts = (*s).data as *mut Sexp;
507        *elts = x;
508    }
509    s
510}
511
512// endregion
513
514// region: String functions
515
516#[no_mangle]
517pub extern "C" fn Rf_mkChar(str_ptr: *const c_char) -> Sexp {
518    if str_ptr.is_null() {
519        return unsafe { R_NaString };
520    }
521    let cstr = unsafe { CStr::from_ptr(str_ptr) };
522    let s = sexp::mk_char(cstr.to_str().unwrap_or(""));
523    track(s);
524    s
525}
526
527#[no_mangle]
528pub extern "C" fn Rf_mkCharLen(str_ptr: *const c_char, len: c_int) -> Sexp {
529    if str_ptr.is_null() {
530        return unsafe { R_NaString };
531    }
532    let bytes = unsafe { std::slice::from_raw_parts(str_ptr as *const u8, len as usize) };
533    let st = std::str::from_utf8(bytes).unwrap_or("");
534    let s = sexp::mk_char(st);
535    track(s);
536    s
537}
538
539#[no_mangle]
540pub extern "C" fn Rf_mkCharCE(str_ptr: *const c_char, _encoding: c_int) -> Sexp {
541    Rf_mkChar(str_ptr) // miniR is always UTF-8
542}
543
544#[no_mangle]
545pub extern "C" fn Rf_getCharCE(_x: Sexp) -> c_int {
546    1 // CE_UTF8
547}
548
549#[no_mangle]
550pub extern "C" fn Rf_mkString(str_ptr: *const c_char) -> Sexp {
551    let s = Rf_allocVector(sexp::STRSXP as c_int, 1);
552    let ch = Rf_mkChar(str_ptr);
553    unsafe {
554        let elts = (*s).data as *mut Sexp;
555        *elts = ch;
556    }
557    s
558}
559
560#[no_mangle]
561pub extern "C" fn Rf_StringBlank(x: Sexp) -> c_int {
562    if x.is_null() {
563        return 1;
564    }
565    unsafe {
566        if x == R_NilValue || x == R_BlankString {
567            return 1;
568        }
569        if (*x).stype != sexp::CHARSXP {
570            return 1;
571        }
572        if (*x).data.is_null() {
573            return 1;
574        }
575        if *((*x).data) == 0 {
576            return 1;
577        }
578    }
579    0
580}
581
582// endregion
583
584// region: Rf_length
585
586#[no_mangle]
587pub extern "C" fn Rf_length(x: Sexp) -> c_int {
588    if x.is_null() {
589        return 0;
590    }
591    unsafe {
592        if x == R_NilValue {
593            return 0;
594        }
595        (*x).length
596    }
597}
598
599// endregion
600
601// region: Symbols
602
603#[no_mangle]
604pub extern "C" fn Rf_install(name: *const c_char) -> Sexp {
605    if name.is_null() {
606        return unsafe { R_NilValue };
607    }
608    let cstr = unsafe { CStr::from_ptr(name) };
609    let s = cstr.to_str().unwrap_or("");
610
611    // Check well-known symbols
612    unsafe {
613        match s {
614            "names" => return R_NamesSymbol,
615            "dim" => return R_DimSymbol,
616            "dimnames" => return R_DimNamesSymbol,
617            "class" => return R_ClassSymbol,
618            "row.names" => return R_RowNamesSymbol,
619            "levels" => return R_LevelsSymbol,
620            "..." => return R_DotsSymbol,
621            _ => {}
622        }
623    }
624
625    // Allocate new symbol
626    let rec = sexp::mk_char(s); // reuse CHARSXP allocator for the name
627    unsafe {
628        (*rec).stype = sexp::SYMSXP;
629    }
630    track(rec);
631    rec
632}
633
634// endregion
635
636// region: Pairlists
637
638#[no_mangle]
639pub extern "C" fn Rf_cons(car: Sexp, cdr: Sexp) -> Sexp {
640    unsafe {
641        let s = calloc(1, std::mem::size_of::<SexpRec>()) as Sexp;
642        if s.is_null() {
643            return R_NilValue;
644        }
645        (*s).stype = sexp::LISTSXP;
646        (*s).attrib = R_NilValue;
647        let pd = calloc(1, std::mem::size_of::<PairlistData>()) as *mut PairlistData;
648        if !pd.is_null() {
649            (*pd).car = car;
650            (*pd).cdr = cdr;
651            (*pd).tag = R_NilValue;
652        }
653        (*s).data = pd as *mut u8;
654        track(s);
655        s
656    }
657}
658
659#[no_mangle]
660pub extern "C" fn Rf_lcons(car: Sexp, cdr: Sexp) -> Sexp {
661    let s = Rf_cons(car, cdr);
662    if !s.is_null() {
663        unsafe {
664            (*s).stype = 6;
665        } // LANGSXP
666    }
667    s
668}
669
670// endregion
671
672// region: PROTECT / UNPROTECT
673
674#[no_mangle]
675pub extern "C" fn Rf_protect(s: Sexp) -> Sexp {
676    STATE.with(|state| {
677        state.borrow_mut().protect_stack.push(s);
678    });
679    s
680}
681
682#[no_mangle]
683pub extern "C" fn Rf_unprotect(n: c_int) {
684    STATE.with(|state| {
685        let mut st = state.borrow_mut();
686        let n = n as usize;
687        let new_len = st.protect_stack.len().saturating_sub(n);
688        st.protect_stack.truncate(new_len);
689    });
690}
691
692// endregion
693
694// region: Type checking
695
696#[no_mangle]
697pub extern "C" fn Rf_isNull(x: Sexp) -> c_int {
698    if x.is_null() {
699        return 1;
700    }
701    (unsafe { (*x).stype } == sexp::NILSXP) as c_int
702}
703
704#[no_mangle]
705pub extern "C" fn Rf_isReal(x: Sexp) -> c_int {
706    if x.is_null() {
707        0
708    } else {
709        (unsafe { (*x).stype } == sexp::REALSXP) as c_int
710    }
711}
712#[no_mangle]
713pub extern "C" fn Rf_isInteger(x: Sexp) -> c_int {
714    if x.is_null() {
715        0
716    } else {
717        (unsafe { (*x).stype } == sexp::INTSXP) as c_int
718    }
719}
720#[no_mangle]
721pub extern "C" fn Rf_isLogical(x: Sexp) -> c_int {
722    if x.is_null() {
723        0
724    } else {
725        (unsafe { (*x).stype } == sexp::LGLSXP) as c_int
726    }
727}
728#[no_mangle]
729pub extern "C" fn Rf_isString(x: Sexp) -> c_int {
730    if x.is_null() {
731        0
732    } else {
733        (unsafe { (*x).stype } == sexp::STRSXP) as c_int
734    }
735}
736
737#[no_mangle]
738pub extern "C" fn Rf_isVector(x: Sexp) -> c_int {
739    if x.is_null() {
740        return 0;
741    }
742    let t = unsafe { (*x).stype };
743    matches!(
744        t,
745        sexp::REALSXP
746            | sexp::INTSXP
747            | sexp::LGLSXP
748            | sexp::STRSXP
749            | sexp::VECSXP
750            | sexp::RAWSXP
751            | sexp::CPLXSXP
752    ) as c_int
753}
754
755#[no_mangle]
756pub extern "C" fn Rf_inherits(x: Sexp, name: *const c_char) -> c_int {
757    if x.is_null() || name.is_null() {
758        return 0;
759    }
760    let target = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("");
761    let klass = Rf_getAttrib(x, unsafe { R_ClassSymbol });
762    if klass.is_null() || unsafe { (*klass).stype } != sexp::STRSXP {
763        return 0;
764    }
765    let len = unsafe { (*klass).length } as usize;
766    for i in 0..len {
767        let elt = unsafe { *((*klass).data as *const Sexp).add(i) };
768        if !elt.is_null() {
769            let s = unsafe { sexp::char_data(elt) };
770            if s == target {
771                return 1;
772            }
773        }
774    }
775    0
776}
777
778// endregion
779
780// region: Attributes
781
782fn sym_eq(a: Sexp, b: Sexp) -> bool {
783    if a == b {
784        return true;
785    }
786    if a.is_null() || b.is_null() {
787        return false;
788    }
789    unsafe {
790        if (*a).stype != sexp::SYMSXP || (*b).stype != sexp::SYMSXP {
791            return false;
792        }
793        if (*a).data.is_null() || (*b).data.is_null() {
794            return false;
795        }
796        let a_str = CStr::from_ptr((*a).data as *const c_char);
797        let b_str = CStr::from_ptr((*b).data as *const c_char);
798        a_str == b_str
799    }
800}
801
802#[no_mangle]
803pub extern "C" fn Rf_getAttrib(x: Sexp, name: Sexp) -> Sexp {
804    if x.is_null() {
805        return unsafe { R_NilValue };
806    }
807    let mut attr = unsafe { (*x).attrib };
808    while !attr.is_null() && unsafe { (*attr).stype } == sexp::LISTSXP {
809        let pd = unsafe { (*attr).data as *const PairlistData };
810        if !pd.is_null() && sym_eq(unsafe { (*pd).tag }, name) {
811            return unsafe { (*pd).car };
812        }
813        attr = if pd.is_null() {
814            ptr::null_mut()
815        } else {
816            unsafe { (*pd).cdr }
817        };
818    }
819    unsafe { R_NilValue }
820}
821
822#[no_mangle]
823pub extern "C" fn Rf_setAttrib(x: Sexp, name: Sexp, val: Sexp) -> Sexp {
824    if x.is_null() {
825        return val;
826    }
827    // Search for existing
828    let mut attr = unsafe { (*x).attrib };
829    while !attr.is_null() && unsafe { (*attr).stype } == sexp::LISTSXP {
830        let pd = unsafe { (*attr).data as *mut PairlistData };
831        if !pd.is_null() && sym_eq(unsafe { (*pd).tag }, name) {
832            unsafe {
833                (*pd).car = val;
834            }
835            return val;
836        }
837        attr = if pd.is_null() {
838            ptr::null_mut()
839        } else {
840            unsafe { (*pd).cdr }
841        };
842    }
843    // Prepend
844    let node = Rf_cons(val, unsafe { (*x).attrib });
845    unsafe {
846        let pd = (*node).data as *mut PairlistData;
847        if !pd.is_null() {
848            (*pd).tag = name;
849        }
850        (*x).attrib = node;
851    }
852    val
853}
854
855// endregion
856
857// region: Coercion
858
859#[no_mangle]
860pub extern "C" fn Rf_asReal(x: Sexp) -> f64 {
861    if x.is_null() {
862        return sexp::NA_REAL;
863    }
864    unsafe {
865        match (*x).stype {
866            sexp::REALSXP if (*x).length > 0 => *((*x).data as *const f64),
867            sexp::INTSXP if (*x).length > 0 => {
868                let v = *((*x).data as *const i32);
869                if v == sexp::NA_INTEGER {
870                    sexp::NA_REAL
871                } else {
872                    f64::from(v)
873                }
874            }
875            sexp::LGLSXP if (*x).length > 0 => {
876                let v = *((*x).data as *const i32);
877                if v == sexp::NA_LOGICAL {
878                    sexp::NA_REAL
879                } else {
880                    f64::from(v)
881                }
882            }
883            _ => sexp::NA_REAL,
884        }
885    }
886}
887
888#[no_mangle]
889pub extern "C" fn Rf_asInteger(x: Sexp) -> c_int {
890    if x.is_null() {
891        return sexp::NA_INTEGER;
892    }
893    unsafe {
894        match (*x).stype {
895            sexp::INTSXP if (*x).length > 0 => *((*x).data as *const i32),
896            sexp::REALSXP if (*x).length > 0 => {
897                let v = *((*x).data as *const f64);
898                if sexp::is_na_real(v) {
899                    sexp::NA_INTEGER
900                } else {
901                    v as i32
902                }
903            }
904            sexp::LGLSXP if (*x).length > 0 => *((*x).data as *const i32),
905            _ => sexp::NA_INTEGER,
906        }
907    }
908}
909
910#[no_mangle]
911pub extern "C" fn Rf_asLogical(x: Sexp) -> c_int {
912    if x.is_null() {
913        return sexp::NA_LOGICAL;
914    }
915    unsafe {
916        match (*x).stype {
917            sexp::LGLSXP if (*x).length > 0 => *((*x).data as *const i32),
918            sexp::INTSXP if (*x).length > 0 => {
919                let v = *((*x).data as *const i32);
920                if v == sexp::NA_INTEGER {
921                    sexp::NA_LOGICAL
922                } else {
923                    (v != 0) as i32
924                }
925            }
926            sexp::REALSXP if (*x).length > 0 => {
927                let v = *((*x).data as *const f64);
928                if sexp::is_na_real(v) {
929                    sexp::NA_LOGICAL
930                } else {
931                    (v != 0.0) as i32
932                }
933            }
934            _ => sexp::NA_LOGICAL,
935        }
936    }
937}
938
939#[no_mangle]
940pub extern "C" fn Rf_coerceVector(x: Sexp, stype: c_int) -> Sexp {
941    if x.is_null() {
942        return unsafe { R_NilValue };
943    }
944    let from = unsafe { (*x).stype };
945    if from == stype as u8 {
946        return x;
947    }
948    let n = unsafe { (*x).length } as isize;
949    let out = Rf_protect(Rf_allocVector(stype, n));
950    for i in 0..(n as usize) {
951        unsafe {
952            match stype as u8 {
953                sexp::REALSXP => {
954                    let dst = ((*out).data as *mut f64).add(i);
955                    *dst = match from {
956                        sexp::INTSXP => {
957                            let v = *((*x).data as *const i32).add(i);
958                            if v == sexp::NA_INTEGER {
959                                sexp::NA_REAL
960                            } else {
961                                f64::from(v)
962                            }
963                        }
964                        sexp::LGLSXP => {
965                            let v = *((*x).data as *const i32).add(i);
966                            if v == sexp::NA_LOGICAL {
967                                sexp::NA_REAL
968                            } else {
969                                f64::from(v)
970                            }
971                        }
972                        _ => sexp::NA_REAL,
973                    };
974                }
975                sexp::INTSXP => {
976                    let dst = ((*out).data as *mut i32).add(i);
977                    *dst = match from {
978                        sexp::REALSXP => {
979                            let v = *((*x).data as *const f64).add(i);
980                            if sexp::is_na_real(v) {
981                                sexp::NA_INTEGER
982                            } else {
983                                v as i32
984                            }
985                        }
986                        sexp::LGLSXP => *((*x).data as *const i32).add(i),
987                        _ => sexp::NA_INTEGER,
988                    };
989                }
990                sexp::LGLSXP => {
991                    let dst = ((*out).data as *mut i32).add(i);
992                    *dst = match from {
993                        sexp::INTSXP => {
994                            let v = *((*x).data as *const i32).add(i);
995                            if v == sexp::NA_INTEGER {
996                                sexp::NA_LOGICAL
997                            } else {
998                                (v != 0) as i32
999                            }
1000                        }
1001                        sexp::REALSXP => {
1002                            let v = *((*x).data as *const f64).add(i);
1003                            if sexp::is_na_real(v) {
1004                                sexp::NA_LOGICAL
1005                            } else {
1006                                (v != 0.0) as i32
1007                            }
1008                        }
1009                        _ => sexp::NA_LOGICAL,
1010                    };
1011                }
1012                _ => {}
1013            }
1014        }
1015    }
1016    Rf_unprotect(1);
1017    out
1018}
1019
1020// endregion
1021
1022// region: Duplication
1023
1024#[no_mangle]
1025pub extern "C" fn Rf_duplicate(x: Sexp) -> Sexp {
1026    if x.is_null() {
1027        return unsafe { R_NilValue };
1028    }
1029    unsafe {
1030        if x == R_NilValue {
1031            return R_NilValue;
1032        }
1033    }
1034    let len = unsafe { (*x).length };
1035    let stype = unsafe { (*x).stype };
1036    let out = Rf_allocVector(stype as c_int, len as isize);
1037    if len > 0 {
1038        let elem_size = match stype {
1039            sexp::REALSXP => 8,
1040            sexp::INTSXP | sexp::LGLSXP => 4,
1041            sexp::RAWSXP => 1,
1042            sexp::CPLXSXP => 16,
1043            sexp::STRSXP | sexp::VECSXP => std::mem::size_of::<Sexp>(),
1044            _ => 0,
1045        };
1046        if elem_size > 0 {
1047            unsafe {
1048                ptr::copy_nonoverlapping((*x).data, (*out).data, len as usize * elem_size);
1049            }
1050        }
1051    }
1052    unsafe {
1053        (*out).attrib = (*x).attrib;
1054    }
1055    out
1056}
1057
1058// endregion
1059
1060// region: External pointers
1061
1062#[repr(C)]
1063struct ExtPtrData {
1064    ptr: *mut c_void,
1065    tag: Sexp,
1066    prot: Sexp,
1067}
1068
1069#[no_mangle]
1070pub extern "C" fn R_MakeExternalPtr(p: *mut c_void, tag: Sexp, prot: Sexp) -> Sexp {
1071    unsafe {
1072        let s = calloc(1, std::mem::size_of::<SexpRec>()) as Sexp;
1073        if s.is_null() {
1074            return R_NilValue;
1075        }
1076        (*s).stype = 22; // EXTPTRSXP
1077        (*s).flags = 1; // persistent — survives _minir_free_allocs
1078        (*s).attrib = R_NilValue;
1079        let d = calloc(1, std::mem::size_of::<ExtPtrData>()) as *mut ExtPtrData;
1080        if !d.is_null() {
1081            (*d).ptr = p;
1082            (*d).tag = tag;
1083            (*d).prot = prot;
1084        }
1085        (*s).data = d as *mut u8;
1086        track(s);
1087        s
1088    }
1089}
1090
1091#[no_mangle]
1092pub extern "C" fn R_ExternalPtrAddr(s: Sexp) -> *mut c_void {
1093    if s.is_null() {
1094        return ptr::null_mut();
1095    }
1096    unsafe {
1097        if (*s).stype != 22 || (*s).data.is_null() {
1098            return ptr::null_mut();
1099        }
1100        (*((*s).data as *const ExtPtrData)).ptr
1101    }
1102}
1103
1104#[no_mangle]
1105pub extern "C" fn R_ExternalPtrTag(s: Sexp) -> Sexp {
1106    if s.is_null() {
1107        return unsafe { R_NilValue };
1108    }
1109    unsafe {
1110        if (*s).stype != 22 || (*s).data.is_null() {
1111            return R_NilValue;
1112        }
1113        (*((*s).data as *const ExtPtrData)).tag
1114    }
1115}
1116
1117#[no_mangle]
1118pub extern "C" fn R_ExternalPtrProtected(s: Sexp) -> Sexp {
1119    if s.is_null() {
1120        return unsafe { R_NilValue };
1121    }
1122    unsafe {
1123        if (*s).stype != 22 || (*s).data.is_null() {
1124            return R_NilValue;
1125        }
1126        (*((*s).data as *const ExtPtrData)).prot
1127    }
1128}
1129
1130#[no_mangle]
1131pub extern "C" fn R_ClearExternalPtr(s: Sexp) {
1132    if !s.is_null() {
1133        unsafe {
1134            if (*s).stype == 22 && !(*s).data.is_null() {
1135                (*((*s).data as *mut ExtPtrData)).ptr = ptr::null_mut();
1136            }
1137        }
1138    }
1139}
1140
1141#[no_mangle]
1142pub extern "C" fn R_SetExternalPtrAddr(s: Sexp, p: *mut c_void) {
1143    if !s.is_null() {
1144        unsafe {
1145            if (*s).stype == 22 && !(*s).data.is_null() {
1146                (*((*s).data as *mut ExtPtrData)).ptr = p;
1147            }
1148        }
1149    }
1150}
1151
1152#[no_mangle]
1153pub extern "C" fn R_RegisterCFinalizer(_s: Sexp, _fun: *const c_void) {
1154    // No-op — miniR doesn't have GC-triggered finalizers
1155}
1156
1157#[no_mangle]
1158pub extern "C" fn R_RegisterCFinalizerEx(_s: Sexp, _fun: *const c_void, _onexit: c_int) {
1159    // No-op
1160}
1161
1162// endregion
1163
1164// region: R_RegisterRoutines
1165
1166#[repr(C)]
1167pub struct RCallMethodDef {
1168    name: *const c_char,
1169    fun: *const (),
1170    num_args: c_int,
1171}
1172
1173/// Wrapper for raw pointers that is Send (safe because we only access from single-threaded contexts).
1174#[derive(Clone, Copy)]
1175pub struct SendPtr(pub *const ());
1176unsafe impl Send for SendPtr {}
1177
1178/// Registered .Call methods — shared across all packages in this runtime.
1179pub static REGISTERED_CALLS: std::sync::Mutex<Vec<(String, SendPtr)>> =
1180    std::sync::Mutex::new(Vec::new());
1181
1182/// Registered .C methods — shared across all packages in this runtime.
1183pub static REGISTERED_C_METHODS: std::sync::Mutex<Vec<(String, SendPtr)>> =
1184    std::sync::Mutex::new(Vec::new());
1185
1186/// R_CMethodDef has the same layout as R_CallMethodDef (name, fun, numArgs).
1187type RCMethodDef = RCallMethodDef;
1188
1189#[no_mangle]
1190pub extern "C" fn R_registerRoutines(
1191    _info: *mut c_void,
1192    c_methods: *const RCMethodDef,
1193    call_methods: *const RCallMethodDef,
1194    _fortran_methods: *const c_void,
1195    external_methods: *const RCallMethodDef,
1196) -> c_int {
1197    // Register .C methods
1198    if !c_methods.is_null() {
1199        let mut reg = REGISTERED_C_METHODS
1200            .lock()
1201            .expect("lock registered C methods");
1202        unsafe {
1203            let mut i = 0;
1204            loop {
1205                let entry = &*c_methods.add(i);
1206                if entry.name.is_null() {
1207                    break;
1208                }
1209                let name = CStr::from_ptr(entry.name)
1210                    .to_str()
1211                    .unwrap_or("")
1212                    .to_string();
1213                if !name.is_empty() {
1214                    reg.push((name, SendPtr(entry.fun)));
1215                }
1216                i += 1;
1217            }
1218        }
1219    }
1220    // Register .Call methods
1221    if !call_methods.is_null() {
1222        register_call_methods(call_methods);
1223    }
1224    // Register .External methods — same structure as .Call, stored in the same table
1225    if !external_methods.is_null() {
1226        register_call_methods(external_methods);
1227    }
1228    1
1229}
1230
1231fn register_call_methods(methods: *const RCallMethodDef) {
1232    let mut reg = REGISTERED_CALLS.lock().expect("lock registered calls");
1233    unsafe {
1234        let mut i = 0;
1235        loop {
1236            let entry = &*methods.add(i);
1237            if entry.name.is_null() {
1238                break;
1239            }
1240            let name = CStr::from_ptr(entry.name)
1241                .to_str()
1242                .unwrap_or("")
1243                .to_string();
1244            if !name.is_empty() {
1245                reg.push((name, SendPtr(entry.fun)));
1246            }
1247            i += 1;
1248        }
1249    }
1250}
1251
1252#[no_mangle]
1253pub extern "C" fn R_useDynamicSymbols(_info: *mut c_void, _value: c_int) {}
1254#[no_mangle]
1255pub extern "C" fn R_forceSymbols(_info: *mut c_void, _value: c_int) {}
1256
1257/// Look up a registered .Call method by name. Returns the function pointer or null.
1258pub fn find_registered_call(name: &str) -> Option<*const ()> {
1259    let reg = REGISTERED_CALLS.lock().expect("lock registered calls");
1260    reg.iter().find(|(n, _)| n == name).map(|(_, ptr)| ptr.0)
1261}
1262
1263/// Look up a registered .C method by name. Returns the function pointer or null.
1264pub fn find_registered_c_method(name: &str) -> Option<*const ()> {
1265    let reg = REGISTERED_C_METHODS
1266        .lock()
1267        .expect("lock registered C methods");
1268    reg.iter().find(|(n, _)| n == name).map(|(_, ptr)| ptr.0)
1269}
1270
1271/// Get all registered .Call method names.
1272pub fn registered_call_names() -> Vec<String> {
1273    let reg = REGISTERED_CALLS.lock().expect("lock registered calls");
1274    reg.iter().map(|(n, _)| n.clone()).collect()
1275}
1276
1277/// Get all registered .C method names.
1278pub fn registered_c_method_names() -> Vec<String> {
1279    let reg = REGISTERED_C_METHODS
1280        .lock()
1281        .expect("lock registered C methods");
1282    reg.iter().map(|(n, _)| n.clone()).collect()
1283}
1284
1285// endregion
1286
1287// region: Cross-package callable registry
1288
1289static CCALLABLE: std::sync::Mutex<Vec<(String, String, SendPtr)>> =
1290    std::sync::Mutex::new(Vec::new());
1291
1292#[no_mangle]
1293pub extern "C" fn R_RegisterCCallable(
1294    package: *const c_char,
1295    name: *const c_char,
1296    fptr: *const (),
1297) {
1298    if package.is_null() || name.is_null() {
1299        return;
1300    }
1301    let pkg = unsafe { CStr::from_ptr(package) }
1302        .to_str()
1303        .unwrap_or("")
1304        .to_string();
1305    let nm = unsafe { CStr::from_ptr(name) }
1306        .to_str()
1307        .unwrap_or("")
1308        .to_string();
1309    let mut reg = CCALLABLE.lock().expect("lock ccallable");
1310    reg.push((pkg, nm, SendPtr(fptr)));
1311}
1312
1313#[no_mangle]
1314pub extern "C" fn R_GetCCallable(package: *const c_char, name: *const c_char) -> *const () {
1315    if package.is_null() || name.is_null() {
1316        return ptr::null();
1317    }
1318    let pkg = unsafe { CStr::from_ptr(package) }.to_str().unwrap_or("");
1319    let nm = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("");
1320    let reg = CCALLABLE.lock().expect("lock ccallable");
1321    // rfind: later registrations (our Rust overrides) take precedence
1322    if let Some(ptr) = reg
1323        .iter()
1324        .rfind(|(p, n, _)| p == pkg && n == nm)
1325        .map(|(_, _, ptr)| ptr.0)
1326    {
1327        return ptr;
1328    }
1329    // If rlang CCallable wasn't registered (because we skip C init),
1330    // return a stub to prevent segfaults from NULL function pointers.
1331    if pkg == "rlang" {
1332        tracing::debug!(name = nm, "returning stub for unregistered rlang CCallable");
1333        // Functions returning const char* need a string, others need a SEXP.
1334        // Dispatch based on known function names.
1335        if nm.contains("type_friendly") || nm.contains("format_error_arg") {
1336            return rlang_ccallable_str_stub as *const ();
1337        }
1338        return rlang_ccallable_sexp_stub as *const ();
1339    }
1340    ptr::null()
1341}
1342
1343/// Stub for rlang CCallable functions that return `const char*`.
1344extern "C" fn rlang_ccallable_str_stub() -> *const c_char {
1345    c"<unknown>".as_ptr()
1346}
1347
1348/// Stub for rlang CCallable functions that return `SEXP`.
1349extern "C" fn rlang_ccallable_sexp_stub() -> Sexp {
1350    unsafe { R_NilValue }
1351}
1352
1353// endregion
1354
1355// region: Memory allocation
1356
1357#[no_mangle]
1358pub extern "C" fn R_alloc(nelem: usize, eltsize: c_int) -> *mut c_char {
1359    let bytes = nelem * eltsize as usize;
1360    unsafe {
1361        let ptr = calloc(1, bytes);
1362        if !ptr.is_null() {
1363            // Track via a dummy SEXP so _minir_free_allocs frees it
1364            let dummy = calloc(1, std::mem::size_of::<SexpRec>()) as Sexp;
1365            if !dummy.is_null() {
1366                (*dummy).stype = sexp::RAWSXP;
1367                (*dummy).data = ptr;
1368                (*dummy).length = bytes as i32;
1369                (*dummy).attrib = R_NilValue;
1370                track(dummy);
1371            }
1372        }
1373        ptr as *mut c_char
1374    }
1375}
1376
1377// endregion
1378
1379// region: Misc
1380
1381/// Rf_lengthgets — resize a vector (copy into a new allocation).
1382#[no_mangle]
1383pub extern "C" fn Rf_lengthgets(x: Sexp, new_len: c_int) -> Sexp {
1384    if x.is_null() {
1385        return unsafe { R_NilValue };
1386    }
1387    let stype = unsafe { (*x).stype };
1388    let old_len = unsafe { (*x).length };
1389    let out = Rf_allocVector(stype as c_int, new_len as isize);
1390    let copy_len = std::cmp::min(old_len, new_len) as usize;
1391    if copy_len > 0 {
1392        let elem_size = match stype {
1393            sexp::REALSXP => 8,
1394            sexp::INTSXP | sexp::LGLSXP => 4,
1395            sexp::RAWSXP => 1,
1396            sexp::CPLXSXP => 16,
1397            sexp::STRSXP | sexp::VECSXP => std::mem::size_of::<Sexp>(),
1398            _ => 0,
1399        };
1400        if elem_size > 0 {
1401            unsafe {
1402                ptr::copy_nonoverlapping((*x).data, (*out).data, copy_len * elem_size);
1403            }
1404        }
1405    }
1406    // Copy attributes
1407    unsafe {
1408        (*out).attrib = (*x).attrib;
1409    }
1410    out
1411}
1412
1413#[no_mangle]
1414pub extern "C" fn R_CheckUserInterrupt() {}
1415
1416#[no_mangle]
1417pub extern "C" fn R_do_slot(obj: Sexp, name: Sexp) -> Sexp {
1418    Rf_getAttrib(obj, name)
1419}
1420
1421#[no_mangle]
1422pub extern "C" fn Rf_nrows(x: Sexp) -> c_int {
1423    let dim = Rf_getAttrib(x, unsafe { R_DimSymbol });
1424    if !dim.is_null() && unsafe { (*dim).stype } == sexp::INTSXP && unsafe { (*dim).length } >= 1 {
1425        return unsafe { *((*dim).data as *const i32) };
1426    }
1427    Rf_length(x)
1428}
1429
1430#[no_mangle]
1431pub extern "C" fn Rf_ncols(x: Sexp) -> c_int {
1432    let dim = Rf_getAttrib(x, unsafe { R_DimSymbol });
1433    if !dim.is_null() && unsafe { (*dim).stype } == sexp::INTSXP && unsafe { (*dim).length } >= 2 {
1434        return unsafe { *((*dim).data as *const i32).add(1) };
1435    }
1436    1
1437}
1438
1439// Rf_eval — evaluate an R expression via interpreter callback.
1440// Handles common patterns: symbol lookup (r_sym("name")) and parsed expressions.
1441#[no_mangle]
1442pub extern "C" fn Rf_eval(expr: Sexp, _env: Sexp) -> Sexp {
1443    if expr.is_null() {
1444        return unsafe { R_NilValue };
1445    }
1446
1447    // Stashed LANGSXP from R_ParseVector (length == -1 sentinel) —
1448    // retrieve the stashed RValue and eval directly via the interpreter callback.
1449    if unsafe { (*expr).stype == sexp::LANGSXP && (*expr).length == -1 } {
1450        let idx = unsafe { (*expr).data as usize };
1451        if let Some(stashed) = get_stashed_rvalue(idx) {
1452            let eval_fn = STATE.with(|state| state.borrow().callbacks.eval_expr);
1453            let result = eval_fn.map(|f| f(&stashed));
1454            return match result {
1455                Some(Ok(val)) => {
1456                    let s = super::convert::rvalue_to_sexp(&val);
1457                    track(s);
1458                    s
1459                }
1460                Some(Err(_)) => unsafe { R_NilValue },
1461                None => unsafe { R_NilValue },
1462            };
1463        }
1464    }
1465
1466    // Pairlist-style LANGSXP (e.g. from Rf_lcons/Rf_lang3) — decompile the
1467    // call into "fn(arg1, arg2, ...)" text and evaluate via parse_text callback.
1468    if unsafe { (*expr).stype == sexp::LANGSXP && (*expr).length != -1 } {
1469        if let Some(call_text) = langsxp_to_text(expr) {
1470            // Extract callbacks without holding the borrow
1471            let (parse_fn, eval_fn) = STATE.with(|state| {
1472                let st = state.borrow();
1473                (st.callbacks.parse_text, st.callbacks.eval_expr)
1474            });
1475            if let (Some(parse), Some(eval)) = (parse_fn, eval_fn) {
1476                let result = match parse(&call_text) {
1477                    Ok(parsed) => Some(eval(&parsed)),
1478                    Err(e) => Some(Err(e)),
1479                };
1480                return match result {
1481                    Some(Ok(val)) => {
1482                        let s = super::convert::rvalue_to_sexp(&val);
1483                        track(s);
1484                        s
1485                    }
1486                    Some(Err(_)) | None => unsafe { R_NilValue },
1487                };
1488            }
1489        }
1490    }
1491
1492    // Convert SEXP to RValue for the callback
1493    let rval = unsafe { super::convert::sexp_to_rvalue(expr) };
1494
1495    // Extract callback function pointers — must not hold the RefCell borrow
1496    // while calling them, since callbacks may re-enter Rf_eval.
1497    let (find_var, eval_expr) = STATE.with(|state| {
1498        let st = state.borrow();
1499        (st.callbacks.find_var, st.callbacks.eval_expr)
1500    });
1501
1502    // Try symbol lookup first (most common case in init functions)
1503    let mut result: Option<
1504        Result<crate::interpreter::value::RValue, crate::interpreter::value::RError>,
1505    > = None;
1506
1507    if let Some(find) = find_var {
1508        // Character vector with one element → look up by name
1509        if let crate::interpreter::value::RValue::Vector(ref rv) = rval {
1510            if let crate::interpreter::value::Vector::Character(ref c) = rv.inner {
1511                if c.len() == 1 {
1512                    if let Some(Some(name)) = c.first() {
1513                        if let Some(val) = find(name) {
1514                            result = Some(Ok(val));
1515                        }
1516                    }
1517                }
1518            }
1519        }
1520        // SYMSXP: the name is in the data field
1521        if result.is_none() {
1522            unsafe {
1523                if (*expr).stype == sexp::SYMSXP && !(*expr).data.is_null() {
1524                    let name = sexp::char_data(expr);
1525                    if !name.is_empty() {
1526                        if let Some(val) = find(name) {
1527                            result = Some(Ok(val));
1528                        }
1529                    }
1530                }
1531            }
1532        }
1533    }
1534
1535    // For general expressions, use the eval callback
1536    if result.is_none() {
1537        if let Some(eval_fn) = eval_expr {
1538            result = Some(eval_fn(&rval));
1539        }
1540    }
1541
1542    match result {
1543        Some(Ok(val)) => {
1544            let s = super::convert::rvalue_to_sexp(&val);
1545            track(s);
1546            s
1547        }
1548        Some(Err(_)) => unsafe { R_NilValue },
1549        None => unsafe { R_NilValue },
1550    }
1551}
1552
1553// R_Serialize stub
1554#[no_mangle]
1555pub extern "C" fn R_Serialize(_s: Sexp, _stream: *mut c_void) {
1556    let _ = std::io::Write::write_all(
1557        &mut std::io::stderr(),
1558        b"Warning: R_Serialize() is a stub in miniR -- serialization from C not supported\n",
1559    );
1560}
1561
1562// Rf_xlength — long vector length (same as Rf_length for non-long vecs)
1563#[no_mangle]
1564pub extern "C" fn Rf_xlength(x: Sexp) -> isize {
1565    Rf_length(x) as isize
1566}
1567
1568// Rf_xlengthgets — resize using long length
1569#[no_mangle]
1570pub extern "C" fn Rf_xlengthgets(x: Sexp, new_len: isize) -> Sexp {
1571    Rf_lengthgets(x, new_len as c_int)
1572}
1573
1574// Rf_mkCharLenCE — create CHARSXP with length and encoding
1575#[no_mangle]
1576pub extern "C" fn Rf_mkCharLenCE(str_ptr: *const c_char, len: c_int, _encoding: c_int) -> Sexp {
1577    Rf_mkCharLen(str_ptr, len)
1578}
1579
1580// Rf_translateChar — identity (miniR is UTF-8)
1581#[no_mangle]
1582pub extern "C" fn Rf_translateChar(x: Sexp) -> *const c_char {
1583    if x.is_null() {
1584        return c"".as_ptr();
1585    }
1586    unsafe { (*x).data as *const c_char }
1587}
1588
1589// classgets — set class attribute (alias for Rf_setAttrib with R_ClassSymbol)
1590#[no_mangle]
1591pub extern "C" fn Rf_classgets(x: Sexp, klass: Sexp) -> Sexp {
1592    Rf_setAttrib(x, unsafe { R_ClassSymbol }, klass);
1593    x
1594}
1595
1596// namesgets — set names attribute
1597#[no_mangle]
1598pub extern "C" fn Rf_namesgets(x: Sexp, names: Sexp) -> Sexp {
1599    Rf_setAttrib(x, unsafe { R_NamesSymbol }, names);
1600    x
1601}
1602
1603// dimgets — set dim attribute
1604#[no_mangle]
1605pub extern "C" fn Rf_dimgets(x: Sexp, dim: Sexp) -> Sexp {
1606    Rf_setAttrib(x, unsafe { R_DimSymbol }, dim);
1607    x
1608}
1609
1610// GetRNGstate / PutRNGstate — no-ops (RNG state is in Rust)
1611#[no_mangle]
1612pub extern "C" fn GetRNGstate() {}
1613#[no_mangle]
1614pub extern "C" fn PutRNGstate() {}
1615
1616// unif_rand — thread-local xorshift64 RNG
1617#[no_mangle]
1618pub extern "C" fn unif_rand() -> f64 {
1619    use std::cell::RefCell;
1620    thread_local! {
1621        static RNG: RefCell<u64> = const { RefCell::new(0x12345678) };
1622    }
1623    RNG.with(|rng| {
1624        let mut state = rng.borrow_mut();
1625        // xorshift64
1626        *state ^= *state << 13;
1627        *state ^= *state >> 7;
1628        *state ^= *state << 17;
1629        (*state as f64) / (u64::MAX as f64)
1630    })
1631}
1632
1633// R_EmptyEnv — stub (points to NilValue)
1634#[no_mangle]
1635pub static mut R_EmptyEnv: Sexp = ptr::null_mut();
1636
1637#[no_mangle]
1638pub static mut R_MissingArg: Sexp = ptr::null_mut();
1639
1640// R_PreserveObject — mark SEXP and its children as persistent (survive free_allocs)
1641#[no_mangle]
1642pub extern "C" fn R_PreserveObject(x: Sexp) {
1643    if x.is_null() {
1644        return;
1645    }
1646    unsafe {
1647        if (*x).flags & 0x01 != 0 {
1648            return; // already preserved — avoid infinite recursion
1649        }
1650        (*x).flags |= 0x01;
1651
1652        // Recursively preserve pairlist children (LANGSXP/LISTSXP chains)
1653        if matches!((*x).stype, sexp::LISTSXP | sexp::LANGSXP) && !(*x).data.is_null() {
1654            let pd = (*x).data as *const PairlistData;
1655            R_PreserveObject((*pd).car);
1656            R_PreserveObject((*pd).cdr);
1657        }
1658        // Preserve VECSXP elements
1659        if (*x).stype == sexp::VECSXP && !(*x).data.is_null() {
1660            let elts = (*x).data as *const Sexp;
1661            for i in 0..(*x).length.max(0) as usize {
1662                R_PreserveObject(*elts.add(i));
1663            }
1664        }
1665        // Preserve attributes
1666        if !(*x).attrib.is_null() {
1667            R_PreserveObject((*x).attrib);
1668        }
1669    }
1670}
1671
1672// R_ReleaseObject — unmark SEXP as persistent
1673#[no_mangle]
1674pub extern "C" fn R_ReleaseObject(x: Sexp) {
1675    if !x.is_null() {
1676        unsafe {
1677            (*x).flags &= !0x01;
1678        }
1679    }
1680}
1681
1682// MARK_NOT_MUTABLE — no-op in miniR
1683#[no_mangle]
1684pub extern "C" fn MARK_NOT_MUTABLE(_x: Sexp) {}
1685
1686// PRENV — promise environment (stub)
1687#[no_mangle]
1688pub extern "C" fn PRENV(_x: Sexp) -> Sexp {
1689    unsafe { R_NilValue }
1690}
1691
1692// PREXPR — promise expression (stub)
1693#[no_mangle]
1694pub extern "C" fn PREXPR(_x: Sexp) -> Sexp {
1695    unsafe { R_NilValue }
1696}
1697
1698// Type checking
1699#[no_mangle]
1700pub extern "C" fn Rf_isVectorAtomic(x: Sexp) -> c_int {
1701    if x.is_null() {
1702        return 0;
1703    }
1704    let t = unsafe { (*x).stype };
1705    matches!(
1706        t,
1707        sexp::REALSXP | sexp::INTSXP | sexp::LGLSXP | sexp::STRSXP | sexp::RAWSXP | sexp::CPLXSXP
1708    ) as c_int
1709}
1710
1711#[no_mangle]
1712pub extern "C" fn Rf_isVectorList(x: Sexp) -> c_int {
1713    if x.is_null() {
1714        return 0;
1715    }
1716    (unsafe { (*x).stype } == sexp::VECSXP) as c_int
1717}
1718
1719#[no_mangle]
1720pub extern "C" fn Rf_isMatrix(x: Sexp) -> c_int {
1721    let dim = Rf_getAttrib(x, unsafe { R_DimSymbol });
1722    (!dim.is_null() && unsafe { (*dim).stype } == sexp::INTSXP && unsafe { (*dim).length } == 2)
1723        as c_int
1724}
1725
1726#[no_mangle]
1727pub extern "C" fn Rf_isNumeric(x: Sexp) -> c_int {
1728    if x.is_null() {
1729        return 0;
1730    }
1731    let t = unsafe { (*x).stype };
1732    matches!(t, sexp::REALSXP | sexp::INTSXP) as c_int
1733}
1734
1735#[no_mangle]
1736pub extern "C" fn Rf_isFunction(x: Sexp) -> c_int {
1737    if x.is_null() {
1738        return 0;
1739    }
1740    let t = unsafe { (*x).stype };
1741    matches!(t, 3 | 7 | 8) as c_int // CLOSXP | SPECIALSXP | BUILTINSXP
1742}
1743
1744#[no_mangle]
1745pub extern "C" fn Rf_isEnvironment(x: Sexp) -> c_int {
1746    if x.is_null() {
1747        return 0;
1748    }
1749    (unsafe { (*x).stype } == 4) as c_int // ENVSXP
1750}
1751
1752// PROTECT_INDEX support
1753#[no_mangle]
1754pub extern "C" fn R_ProtectWithIndex(s: Sexp, pi: *mut c_int) {
1755    Rf_protect(s);
1756    STATE.with(|state| {
1757        let st = state.borrow();
1758        if !pi.is_null() {
1759            unsafe {
1760                *pi = (st.protect_stack.len() - 1) as c_int;
1761            }
1762        }
1763    });
1764}
1765
1766#[no_mangle]
1767pub extern "C" fn R_Reprotect(s: Sexp, i: c_int) {
1768    STATE.with(|state| {
1769        let mut st = state.borrow_mut();
1770        let idx = i as usize;
1771        if idx < st.protect_stack.len() {
1772            st.protect_stack[idx] = s;
1773        }
1774    });
1775}
1776
1777// Rf_findVar — look up a variable via interpreter callback
1778#[no_mangle]
1779pub extern "C" fn Rf_findVar(sym: Sexp, env: Sexp) -> Sexp {
1780    if sym.is_null() {
1781        return unsafe { R_UnboundValue };
1782    }
1783
1784    // If env is an ENVSXP, try to extract the miniR Environment and look up in it
1785    if !env.is_null() && unsafe { (*env).stype } == sexp::ENVSXP {
1786        let var_name = unsafe { sexp::char_data(sym) };
1787        if let Some(e) = unsafe { super::convert::env_from_sexp(env) } {
1788            if let Some(val) = e.get(var_name) {
1789                let s = super::convert::rvalue_to_sexp(&val);
1790                track(s);
1791                return s;
1792            }
1793        }
1794        // Also check pairlist-style bindings (C-created ENVSXP)
1795        let mut node = unsafe { (*env).attrib };
1796        while !node.is_null() && unsafe { (*node).stype } == sexp::LISTSXP {
1797            let pd = unsafe { (*node).data as *const sexp::PairlistData };
1798            if !pd.is_null() && sym_eq(unsafe { (*pd).tag }, sym) {
1799                return unsafe { (*pd).car };
1800            }
1801            node = if pd.is_null() {
1802                ptr::null_mut()
1803            } else {
1804                unsafe { (*pd).cdr }
1805            };
1806        }
1807        return unsafe { R_UnboundValue };
1808    }
1809
1810    // Extract variable name from the symbol SEXP
1811    let name = unsafe { sexp::char_data(sym) };
1812    if name.is_empty() {
1813        return unsafe { R_UnboundValue };
1814    }
1815    // Try the interpreter callback
1816    let result = STATE.with(|state| {
1817        let st = state.borrow();
1818        if let Some(find) = st.callbacks.find_var {
1819            find(name)
1820        } else {
1821            None
1822        }
1823    });
1824    match result {
1825        Some(val) => {
1826            // Convert RValue back to SEXP for C code
1827            let s = super::convert::rvalue_to_sexp(&val);
1828            track(s);
1829            s
1830        }
1831        None => unsafe { R_UnboundValue },
1832    }
1833}
1834
1835#[no_mangle]
1836pub extern "C" fn Rf_findVarInFrame3(env: Sexp, sym: Sexp, _inherits: c_int) -> Sexp {
1837    Rf_findVar(sym, env)
1838}
1839
1840// R_ExecWithCleanup — execute function with cleanup
1841#[no_mangle]
1842pub extern "C" fn R_ExecWithCleanup(
1843    fun: Option<unsafe extern "C" fn(*mut c_void) -> Sexp>,
1844    data: *mut c_void,
1845    cleanup: Option<unsafe extern "C" fn(*mut c_void)>,
1846    cleandata: *mut c_void,
1847) -> Sexp {
1848    let result = match fun {
1849        Some(f) => unsafe { f(data) },
1850        None => unsafe { R_NilValue },
1851    };
1852    if let Some(c) = cleanup {
1853        unsafe {
1854            c(cleandata);
1855        }
1856    }
1857    result
1858}
1859
1860// R_ExternalPtrAddrFn — same as R_ExternalPtrAddr but returns fn ptr
1861#[no_mangle]
1862pub extern "C" fn R_ExternalPtrAddrFn(s: Sexp) -> *mut c_void {
1863    R_ExternalPtrAddr(s)
1864}
1865
1866// Rf_lang1..4 — construct language call objects
1867#[no_mangle]
1868pub extern "C" fn Rf_lang1(s: Sexp) -> Sexp {
1869    Rf_lcons(s, unsafe { R_NilValue })
1870}
1871
1872#[no_mangle]
1873pub extern "C" fn Rf_lang2(s: Sexp, t: Sexp) -> Sexp {
1874    Rf_lcons(s, Rf_cons(t, unsafe { R_NilValue }))
1875}
1876
1877#[no_mangle]
1878pub extern "C" fn Rf_lang3(s: Sexp, t: Sexp, u: Sexp) -> Sexp {
1879    Rf_lcons(s, Rf_cons(t, Rf_cons(u, unsafe { R_NilValue })))
1880}
1881
1882#[no_mangle]
1883pub extern "C" fn Rf_lang4(s: Sexp, t: Sexp, u: Sexp, v: Sexp) -> Sexp {
1884    Rf_lcons(s, Rf_cons(t, Rf_cons(u, Rf_cons(v, unsafe { R_NilValue }))))
1885}
1886
1887// S_alloc — same as R_alloc but zeroed (already zeroed by calloc)
1888#[no_mangle]
1889pub extern "C" fn S_alloc(nelem: isize, eltsize: c_int) -> *mut c_char {
1890    R_alloc(nelem as usize, eltsize)
1891}
1892
1893// Rf_type2char — type name as string
1894#[no_mangle]
1895pub extern "C" fn Rf_type2char(stype: c_int) -> *const c_char {
1896    match stype as u8 {
1897        sexp::NILSXP => c"NULL".as_ptr(),
1898        sexp::LGLSXP => c"logical".as_ptr(),
1899        sexp::INTSXP => c"integer".as_ptr(),
1900        sexp::REALSXP => c"double".as_ptr(),
1901        sexp::CPLXSXP => c"complex".as_ptr(),
1902        sexp::STRSXP => c"character".as_ptr(),
1903        sexp::VECSXP => c"list".as_ptr(),
1904        sexp::RAWSXP => c"raw".as_ptr(),
1905        _ => c"unknown".as_ptr(),
1906    }
1907}
1908
1909// R_FINITE — exported as function for packages that don't include Arith.h
1910#[no_mangle]
1911pub extern "C" fn R_finite(x: f64) -> c_int {
1912    x.is_finite() as c_int
1913}
1914
1915// Rf_nchar — string length
1916#[no_mangle]
1917pub extern "C" fn Rf_nchar(
1918    x: Sexp,
1919    _ntype: c_int,
1920    _allow_na: c_int,
1921    _keep_na: c_int,
1922    _msg_name: *const c_char,
1923) -> c_int {
1924    if x.is_null() {
1925        return 0;
1926    }
1927    unsafe {
1928        if (*x).stype == sexp::CHARSXP && !(*x).data.is_null() {
1929            let s = CStr::from_ptr((*x).data as *const c_char);
1930            s.to_bytes().len() as c_int
1931        } else {
1932            0
1933        }
1934    }
1935}
1936
1937// Rf_isFrame — check if data.frame
1938#[no_mangle]
1939pub extern "C" fn Rf_isFrame(x: Sexp) -> c_int {
1940    Rf_inherits(x, c"data.frame".as_ptr())
1941}
1942
1943// R_check_class_etc — check if x inherits from any class in valid[]
1944// Returns -1 if not found, otherwise the index in valid[].
1945// Handles both explicit class attributes AND implicit classes:
1946//   "matrix" — has dim attribute with length 2
1947//   "array"  — has dim attribute with length > 2
1948//   "numeric" — REALSXP
1949//   "integer" — INTSXP
1950#[no_mangle]
1951pub extern "C" fn R_check_class_etc(x: Sexp, valid: *const *const c_char) -> c_int {
1952    if x.is_null() || valid.is_null() {
1953        return -1;
1954    }
1955
1956    // Collect implicit classes for this object
1957    let stype = if x.is_null() {
1958        0
1959    } else {
1960        unsafe { (*x).stype }
1961    };
1962    let dim_sym = Rf_install(c"dim".as_ptr());
1963    let dim = Rf_getAttrib(x, dim_sym);
1964    let dim_len = if dim.is_null() {
1965        0
1966    } else {
1967        (unsafe { (*dim).length }) as usize
1968    };
1969
1970    let mut i = 0;
1971    loop {
1972        let class_ptr = unsafe { *valid.add(i) };
1973        if class_ptr.is_null() {
1974            break;
1975        }
1976        // Check explicit class attribute
1977        if Rf_inherits(x, class_ptr) != 0 {
1978            return i as c_int;
1979        }
1980        // Check implicit classes
1981        if let Ok(name) = unsafe { CStr::from_ptr(class_ptr) }.to_str() {
1982            let matches = match name {
1983                "matrix" => dim_len == 2,
1984                "array" => dim_len > 0,
1985                "numeric" => stype == sexp::REALSXP,
1986                "integer" => stype == sexp::INTSXP,
1987                "logical" => stype == sexp::LGLSXP,
1988                "character" => stype == sexp::STRSXP,
1989                "complex" => stype == sexp::CPLXSXP,
1990                "raw" => stype == sexp::RAWSXP,
1991                "list" => stype == sexp::VECSXP,
1992                _ => false,
1993            };
1994            if matches {
1995                return i as c_int;
1996            }
1997        }
1998        i += 1;
1999    }
2000    -1
2001}
2002
2003// R_new_custom_connection — create a custom R connection object.
2004// Returns an INTSXP SEXP representing the connection index.
2005// The Rconnection pointer is written to *ptr so C code can set up callbacks.
2006#[no_mangle]
2007pub extern "C" fn R_new_custom_connection(
2008    _description: *const c_char,
2009    _mode: *const c_char,
2010    _class_name: *const c_char,
2011    ptr: *mut *mut SexpRec, // Rconnection* — pointer to Rconn struct
2012) -> Sexp {
2013    // Allocate a zeroed Rconn struct so callers can safely write fields.
2014    // Use the C allocator directly — this is freed when the connection is closed.
2015    let layout = std::alloc::Layout::from_size_align(512, 8).unwrap();
2016    let conn = unsafe { std::alloc::alloc_zeroed(layout) as *mut SexpRec };
2017    if !ptr.is_null() {
2018        unsafe { *ptr = conn };
2019    }
2020    // Return a minimal INTSXP with connection index 3 (first user connection)
2021    let sexp = sexp::alloc_vector(sexp::INTSXP, 1);
2022    if !sexp.is_null() {
2023        let data = unsafe { (*sexp).data as *mut i64 };
2024        if !data.is_null() {
2025            unsafe { *data = 3 };
2026        }
2027    }
2028    sexp
2029}
2030
2031// R_GetConnection — get the Rconnection pointer from a connection SEXP.
2032// Returns NULL for now since we don't track real connections.
2033#[no_mangle]
2034pub extern "C" fn R_GetConnection(_con: Sexp) -> *mut SexpRec {
2035    ptr::null_mut()
2036}
2037
2038// Rf_copyMostAttrib — copy attributes from one SEXP to another
2039#[no_mangle]
2040pub extern "C" fn Rf_copyMostAttrib(from: Sexp, to: Sexp) {
2041    if from.is_null() || to.is_null() {
2042        return;
2043    }
2044    unsafe {
2045        (*to).attrib = (*from).attrib;
2046    }
2047}
2048
2049// Rf_nthcdr — walk n steps down a pairlist
2050#[no_mangle]
2051pub extern "C" fn Rf_nthcdr(mut s: Sexp, n: c_int) -> Sexp {
2052    for _ in 0..n {
2053        if s.is_null() {
2054            return unsafe { R_NilValue };
2055        }
2056        unsafe {
2057            if (*s).stype == sexp::LISTSXP && !(*s).data.is_null() {
2058                s = (*((*s).data as *const PairlistData)).cdr;
2059            } else {
2060                return R_NilValue;
2061            }
2062        }
2063    }
2064    s
2065}
2066
2067// R_FlushConsole — no-op
2068#[no_mangle]
2069pub extern "C" fn R_FlushConsole() {}
2070
2071// R_do_slot_assign -- slot assignment stub (returns obj for chaining)
2072#[no_mangle]
2073pub extern "C" fn R_do_slot_assign(obj: Sexp, name: Sexp, val: Sexp) -> Sexp {
2074    Rf_setAttrib(obj, name, val);
2075    obj
2076}
2077
2078// Rf_allocList — allocate a pairlist of n nodes
2079#[no_mangle]
2080pub extern "C" fn Rf_allocList(n: c_int) -> Sexp {
2081    let mut result = unsafe { R_NilValue };
2082    for _ in 0..n {
2083        result = Rf_cons(unsafe { R_NilValue }, result);
2084    }
2085    result
2086}
2087
2088// Rf_match — match values (stub returns vector of nomatch)
2089#[no_mangle]
2090pub extern "C" fn Rf_match(_table: Sexp, x: Sexp, nomatch: c_int) -> Sexp {
2091    let n = if x.is_null() {
2092        0
2093    } else {
2094        (unsafe { (*x).length }) as isize
2095    };
2096    let result = Rf_allocVector(sexp::INTSXP as c_int, n);
2097    if n > 0 {
2098        unsafe {
2099            let ptr = (*result).data as *mut i32;
2100            for i in 0..n as usize {
2101                *ptr.add(i) = nomatch;
2102            }
2103        }
2104    }
2105    result
2106}
2107
2108// Rf_asCharacterFactor — convert factor to character (stub)
2109#[no_mangle]
2110pub extern "C" fn Rf_asCharacterFactor(_x: Sexp) -> Sexp {
2111    Rf_allocVector(sexp::STRSXP as c_int, 0)
2112}
2113
2114// R_isort — integer sort (in-place)
2115#[no_mangle]
2116pub extern "C" fn R_isort(x: *mut c_int, n: c_int) {
2117    if x.is_null() || n <= 0 {
2118        return;
2119    }
2120    let slice = unsafe { std::slice::from_raw_parts_mut(x, n as usize) };
2121    slice.sort_unstable();
2122}
2123
2124// R_rsort — double sort (in-place)
2125#[no_mangle]
2126pub extern "C" fn R_rsort(x: *mut f64, n: c_int) {
2127    if x.is_null() || n <= 0 {
2128        return;
2129    }
2130    let slice = unsafe { std::slice::from_raw_parts_mut(x, n as usize) };
2131    slice.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2132}
2133
2134// revsort — sort x descending, carrying along index
2135#[no_mangle]
2136pub extern "C" fn revsort(x: *mut f64, index: *mut c_int, n: c_int) {
2137    if x.is_null() || n <= 0 {
2138        return;
2139    }
2140    let xs = unsafe { std::slice::from_raw_parts_mut(x, n as usize) };
2141    let mut is = if index.is_null() {
2142        None
2143    } else {
2144        Some(unsafe { std::slice::from_raw_parts_mut(index, n as usize) })
2145    };
2146    // Create index pairs and sort descending by value
2147    let mut pairs: Vec<(f64, i32)> = xs
2148        .iter()
2149        .enumerate()
2150        .map(|(i, &v)| (v, is.as_ref().map_or(i as i32, |idx| idx[i])))
2151        .collect();
2152    pairs.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
2153    for (i, (v, idx)) in pairs.into_iter().enumerate() {
2154        xs[i] = v;
2155        if let Some(ref mut is) = is {
2156            is[i] = idx;
2157        }
2158    }
2159}
2160
2161// Rf_installTrChar — install symbol from translated CHARSXP (same as installChar in UTF-8)
2162#[no_mangle]
2163pub extern "C" fn Rf_installTrChar(x: Sexp) -> Sexp {
2164    Rf_installChar(x)
2165}
2166
2167// R_NameSymbol
2168#[no_mangle]
2169pub static mut R_NameSymbol: Sexp = ptr::null_mut();
2170
2171// rsort_with_index
2172#[no_mangle]
2173pub extern "C" fn rsort_with_index(x: *mut f64, index: *mut c_int, n: c_int) {
2174    revsort(x, index, n);
2175}
2176
2177// R_qsort_I — indexed sort ascending
2178#[no_mangle]
2179pub extern "C" fn R_qsort_I(v: *mut f64, _ii: *mut c_int, lo: c_int, hi: c_int) {
2180    if v.is_null() || lo >= hi {
2181        return;
2182    }
2183    let n = (hi - lo + 1) as usize;
2184    let offset = lo.max(1) as usize - 1;
2185    let vs = unsafe { std::slice::from_raw_parts_mut(v.add(offset), n) };
2186    vs.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2187}
2188
2189#[no_mangle]
2190pub extern "C" fn R_qsort_int_I(v: *mut c_int, _ii: *mut c_int, lo: c_int, hi: c_int) {
2191    if v.is_null() || lo >= hi {
2192        return;
2193    }
2194    let n = (hi - lo + 1) as usize;
2195    let offset = lo.max(1) as usize - 1;
2196    let vs = unsafe { std::slice::from_raw_parts_mut(v.add(offset), n) };
2197    vs.sort_unstable();
2198}
2199
2200// R_qsort / R_qsort_int
2201#[no_mangle]
2202pub extern "C" fn R_qsort(v: *mut f64, lo: c_int, hi: c_int) {
2203    R_qsort_I(v, ptr::null_mut(), lo, hi);
2204}
2205#[no_mangle]
2206pub extern "C" fn R_qsort_int(v: *mut c_int, lo: c_int, hi: c_int) {
2207    R_qsort_int_I(v, ptr::null_mut(), lo, hi);
2208}
2209
2210// Rf_dimnamesgets
2211#[no_mangle]
2212pub extern "C" fn Rf_dimnamesgets(x: Sexp, val: Sexp) -> Sexp {
2213    Rf_setAttrib(x, unsafe { R_DimNamesSymbol }, val);
2214    x
2215}
2216
2217// BLAS stubs
2218#[no_mangle]
2219pub extern "C" fn dgemv_(
2220    _trans: *const u8,
2221    _m: *const c_int,
2222    _n: *const c_int,
2223    _alpha: *const f64,
2224    _a: *const f64,
2225    _lda: *const c_int,
2226    _x: *const f64,
2227    _incx: *const c_int,
2228    _beta: *const f64,
2229    _y: *mut f64,
2230    _incy: *const c_int,
2231) {
2232}
2233#[no_mangle]
2234pub extern "C" fn dpotrf_(
2235    _uplo: *const u8,
2236    _n: *const c_int,
2237    _a: *mut f64,
2238    _lda: *const c_int,
2239    _info: *mut c_int,
2240) {
2241}
2242#[no_mangle]
2243pub extern "C" fn dpotri_(
2244    _uplo: *const u8,
2245    _n: *const c_int,
2246    _a: *mut f64,
2247    _lda: *const c_int,
2248    _info: *mut c_int,
2249) {
2250}
2251#[no_mangle]
2252pub extern "C" fn dtrsm_(
2253    _side: *const u8,
2254    _uplo: *const u8,
2255    _transa: *const u8,
2256    _diag: *const u8,
2257    _m: *const c_int,
2258    _n: *const c_int,
2259    _alpha: *const f64,
2260    _a: *const f64,
2261    _lda: *const c_int,
2262    _b: *mut f64,
2263    _ldb: *const c_int,
2264) {
2265}
2266
2267// Rf_allocArray — allocate array with dimensions
2268#[no_mangle]
2269pub extern "C" fn Rf_allocArray(stype: c_int, dims: Sexp) -> Sexp {
2270    // Compute total size from dims vector
2271    let total = if dims.is_null() {
2272        0
2273    } else {
2274        let n = unsafe { (*dims).length } as usize;
2275        let mut product: isize = 1;
2276        for i in 0..n {
2277            let d = unsafe { *((*dims).data as *const i32).add(i) } as isize;
2278            product *= d;
2279        }
2280        product
2281    };
2282    let result = Rf_allocVector(stype, total);
2283    Rf_setAttrib(result, unsafe { R_DimSymbol }, dims);
2284    result
2285}
2286
2287// Fortran LAPACK/BLAS stubs — empty implementations that prevent link errors.
2288// Packages that actually USE these routines will get wrong results, but at
2289// least they compile and load (non-LAPACK functionality still works).
2290#[no_mangle]
2291pub extern "C" fn dqrdc2_(
2292    _x: *mut f64,
2293    _ldx: *const c_int,
2294    _n: *const c_int,
2295    _p: *const c_int,
2296    _tol: *const f64,
2297    _k: *mut c_int,
2298    _qraux: *mut f64,
2299    _jpvt: *mut c_int,
2300    _work: *mut f64,
2301) {
2302    std::io::Write::write_all(
2303        &mut std::io::stderr(),
2304        b"Warning: dqrdc2_ is a stub in miniR\n",
2305    )
2306    .ok();
2307}
2308
2309#[no_mangle]
2310pub extern "C" fn dqrsl_(
2311    _x: *mut f64,
2312    _ldx: *const c_int,
2313    _n: *const c_int,
2314    _k: *const c_int,
2315    _qraux: *mut f64,
2316    _y: *mut f64,
2317    _qy: *mut f64,
2318    _qty: *mut f64,
2319    _b: *mut f64,
2320    _rsd: *mut f64,
2321    _xb: *mut f64,
2322    _job: *const c_int,
2323    _info: *mut c_int,
2324) {
2325}
2326
2327#[no_mangle]
2328pub extern "C" fn dgemm_(
2329    _transa: *const u8,
2330    _transb: *const u8,
2331    _m: *const c_int,
2332    _n: *const c_int,
2333    _k: *const c_int,
2334    _alpha: *const f64,
2335    _a: *const f64,
2336    _lda: *const c_int,
2337    _b: *const f64,
2338    _ldb: *const c_int,
2339    _beta: *const f64,
2340    _c: *mut f64,
2341    _ldc: *const c_int,
2342) {
2343}
2344
2345#[no_mangle]
2346pub extern "C" fn dsyrk_(
2347    _uplo: *const u8,
2348    _trans: *const u8,
2349    _n: *const c_int,
2350    _k: *const c_int,
2351    _alpha: *const f64,
2352    _a: *const f64,
2353    _lda: *const c_int,
2354    _beta: *const f64,
2355    _c: *mut f64,
2356    _ldc: *const c_int,
2357) {
2358}
2359
2360// More LAPACK/LINPACK stubs
2361#[no_mangle]
2362pub extern "C" fn dtrsl_(
2363    _t: *mut f64,
2364    _ldt: *const c_int,
2365    _n: *const c_int,
2366    _b: *mut f64,
2367    _job: *const c_int,
2368    _info: *mut c_int,
2369) {
2370}
2371#[no_mangle]
2372pub extern "C" fn chol_(
2373    _a: *mut f64,
2374    _lda: *const c_int,
2375    _n: *const c_int,
2376    _v: *mut f64,
2377    _info: *mut c_int,
2378) {
2379}
2380#[no_mangle]
2381pub extern "C" fn rs_(
2382    _nm: *const c_int,
2383    _n: *const c_int,
2384    _a: *mut f64,
2385    _w: *mut f64,
2386    _matz: *const c_int,
2387    _z: *mut f64,
2388    _fv1: *mut f64,
2389    _fv2: *mut f64,
2390    _ierr: *mut c_int,
2391) {
2392}
2393
2394// R math stubs — distribution functions
2395#[no_mangle]
2396pub extern "C" fn dnorm(_x: f64, _mu: f64, _sigma: f64, _log_p: c_int) -> f64 {
2397    f64::NAN
2398}
2399#[no_mangle]
2400pub extern "C" fn pnorm(_x: f64, _mu: f64, _sigma: f64, _lt: c_int, _lp: c_int) -> f64 {
2401    f64::NAN
2402}
2403#[no_mangle]
2404pub extern "C" fn qnorm(_p: f64, _mu: f64, _sigma: f64, _lt: c_int, _lp: c_int) -> f64 {
2405    f64::NAN
2406}
2407#[no_mangle]
2408pub extern "C" fn qchisq(_p: f64, _df: f64, _lt: c_int, _lp: c_int) -> f64 {
2409    f64::NAN
2410}
2411#[no_mangle]
2412pub extern "C" fn rexp(_scale: f64) -> f64 {
2413    exp_rand() * _scale
2414}
2415#[no_mangle]
2416pub extern "C" fn rnorm(_mu: f64, _sigma: f64) -> f64 {
2417    _mu + _sigma * norm_rand()
2418}
2419#[no_mangle]
2420pub extern "C" fn runif(a: f64, b: f64) -> f64 {
2421    a + (b - a) * unif_rand()
2422}
2423#[no_mangle]
2424pub extern "C" fn rpois(_lambda: f64) -> f64 {
2425    _lambda
2426} // stub
2427#[no_mangle]
2428pub extern "C" fn rbinom(_n: f64, _p: f64) -> f64 {
2429    _n * _p
2430} // stub
2431#[no_mangle]
2432pub extern "C" fn choose(n: f64, k: f64) -> f64 {
2433    if k < 0.0 || k > n {
2434        return 0.0;
2435    }
2436    lgammafn(n + 1.0) - lgammafn(k + 1.0) - lgammafn(n - k + 1.0)
2437}
2438#[no_mangle]
2439pub extern "C" fn lchoose(n: f64, k: f64) -> f64 {
2440    choose(n, k).ln()
2441}
2442#[no_mangle]
2443pub extern "C" fn lgammafn(x: f64) -> f64 {
2444    libm::lgamma(x)
2445}
2446#[no_mangle]
2447pub extern "C" fn gammafn(x: f64) -> f64 {
2448    libm::tgamma(x)
2449}
2450#[no_mangle]
2451pub extern "C" fn beta(a: f64, b: f64) -> f64 {
2452    (lgammafn(a) + lgammafn(b) - lgammafn(a + b)).exp()
2453}
2454#[no_mangle]
2455pub extern "C" fn lbeta(a: f64, b: f64) -> f64 {
2456    lgammafn(a) + lgammafn(b) - lgammafn(a + b)
2457}
2458#[no_mangle]
2459pub extern "C" fn dbinom(_x: f64, _n: f64, _p: f64, _lg: c_int) -> f64 {
2460    f64::NAN
2461}
2462#[no_mangle]
2463pub extern "C" fn dpois(_x: f64, _lambda: f64, _lg: c_int) -> f64 {
2464    f64::NAN
2465}
2466#[no_mangle]
2467pub extern "C" fn pgamma(_x: f64, _shape: f64, _scale: f64, _lt: c_int, _lp: c_int) -> f64 {
2468    f64::NAN
2469}
2470#[no_mangle]
2471pub extern "C" fn qgamma(_p: f64, _shape: f64, _scale: f64, _lt: c_int, _lp: c_int) -> f64 {
2472    f64::NAN
2473}
2474
2475// R_atof — parse double
2476#[no_mangle]
2477pub extern "C" fn R_atof(str_ptr: *const c_char) -> f64 {
2478    if str_ptr.is_null() {
2479        return 0.0;
2480    }
2481    let s = unsafe { CStr::from_ptr(str_ptr) }.to_str().unwrap_or("0");
2482    s.parse::<f64>().unwrap_or(0.0)
2483}
2484
2485// optif9 — optimization routine stub (27 args)
2486#[no_mangle]
2487pub extern "C" fn optif9(
2488    _nr: *const c_int,
2489    _n: *const c_int,
2490    _x: *mut f64,
2491    _fcn: *const (),
2492    _d1fcn: *const (),
2493    _d2fcn: *const (),
2494    _typsiz: *mut f64,
2495    _fscale: *const f64,
2496    _method: *const c_int,
2497    _iexp: *const c_int,
2498    _msg: *mut c_int,
2499    _ndigit: *const c_int,
2500    _itnlim: *const c_int,
2501    _iagflg: *const c_int,
2502    _iahflg: *const c_int,
2503    _dlt: *const f64,
2504    _gradtl: *const f64,
2505    _stepmx: *const f64,
2506    _steptl: *const f64,
2507    _xpls: *mut f64,
2508    _fpls: *mut f64,
2509    _gpls: *mut f64,
2510    _itrmcd: *mut c_int,
2511    _a: *mut f64,
2512    _wrk: *mut f64,
2513) {
2514}
2515
2516// R_do_MAKE_CLASS — create an S4 class object (stub)
2517#[no_mangle]
2518pub extern "C" fn R_do_MAKE_CLASS(_name: *const c_char) -> Sexp {
2519    Rf_allocVector(sexp::VECSXP as c_int, 0)
2520}
2521
2522// iPsort / rPsort — partial sort (sort enough to get k-th element)
2523#[no_mangle]
2524pub extern "C" fn iPsort(x: *mut c_int, n: c_int, _k: c_int) {
2525    R_isort(x, n); // full sort as fallback
2526}
2527#[no_mangle]
2528pub extern "C" fn rPsort(x: *mut f64, n: c_int, _k: c_int) {
2529    R_rsort(x, n);
2530}
2531
2532// Rf_isPrimitive
2533#[no_mangle]
2534pub extern "C" fn Rf_isPrimitive(x: Sexp) -> c_int {
2535    if x.is_null() {
2536        return 0;
2537    }
2538    let t = unsafe { (*x).stype };
2539    matches!(t, 7 | 8) as c_int // SPECIALSXP | BUILTINSXP
2540}
2541
2542// Rf_isSymbol
2543#[no_mangle]
2544pub extern "C" fn Rf_isSymbol(x: Sexp) -> c_int {
2545    if x.is_null() {
2546        return 0;
2547    }
2548    (unsafe { (*x).stype } == sexp::SYMSXP) as c_int
2549}
2550
2551// Rf_lang5/6
2552#[no_mangle]
2553pub extern "C" fn Rf_lang5(s: Sexp, t: Sexp, u: Sexp, v: Sexp, w: Sexp) -> Sexp {
2554    Rf_lcons(
2555        s,
2556        Rf_cons(t, Rf_cons(u, Rf_cons(v, Rf_cons(w, unsafe { R_NilValue })))),
2557    )
2558}
2559
2560#[no_mangle]
2561pub extern "C" fn Rf_lang6(s: Sexp, t: Sexp, u: Sexp, v: Sexp, w: Sexp, x: Sexp) -> Sexp {
2562    Rf_lcons(
2563        s,
2564        Rf_cons(
2565            t,
2566            Rf_cons(u, Rf_cons(v, Rf_cons(w, Rf_cons(x, unsafe { R_NilValue })))),
2567        ),
2568    )
2569}
2570
2571// Rf_findFun — find a function (stub — delegates to Rf_findVar)
2572#[no_mangle]
2573pub extern "C" fn Rf_findFun(sym: Sexp, env: Sexp) -> Sexp {
2574    Rf_findVar(sym, env)
2575}
2576
2577// R_tryEval — evaluate with error flag (stub delegates to Rf_eval)
2578#[no_mangle]
2579pub extern "C" fn R_tryEval(expr: Sexp, env: Sexp, error_occurred: *mut c_int) -> Sexp {
2580    let result = Rf_eval(expr, env);
2581    if !error_occurred.is_null() {
2582        unsafe {
2583            *error_occurred = 0;
2584        }
2585    }
2586    result
2587}
2588
2589// R_forceAndCall — call with n args forced eagerly.
2590// In GNU R this is an optimization hint for the evaluator.
2591// We just delegate to Rf_eval since our eval already forces args.
2592#[no_mangle]
2593pub extern "C" fn R_forceAndCall(call: Sexp, _n: c_int, env: Sexp) -> Sexp {
2594    Rf_eval(call, env)
2595}
2596
2597// R_tryEvalSilent — same as R_tryEval (no error printing)
2598#[no_mangle]
2599pub extern "C" fn R_tryEvalSilent(expr: Sexp, env: Sexp, error_occurred: *mut c_int) -> Sexp {
2600    R_tryEval(expr, env, error_occurred)
2601}
2602
2603// R_ToplevelExec — execute function, return 1 (TRUE) on success
2604#[no_mangle]
2605pub extern "C" fn R_ToplevelExec(
2606    fun: Option<unsafe extern "C" fn(*mut c_void)>,
2607    data: *mut c_void,
2608) -> c_int {
2609    if let Some(f) = fun {
2610        unsafe {
2611            f(data);
2612        }
2613    }
2614    1 // TRUE — success
2615}
2616
2617// Rf_mkNamed — allocate a named VECSXP/list
2618#[no_mangle]
2619pub extern "C" fn Rf_mkNamed(stype: c_int, names: *const *const c_char) -> Sexp {
2620    // Count names (null-terminated array, terminated by "" entry)
2621    let mut n: usize = 0;
2622    if !names.is_null() {
2623        unsafe {
2624            loop {
2625                let name_ptr = *names.add(n);
2626                if name_ptr.is_null() || *name_ptr == 0 {
2627                    break;
2628                }
2629                n += 1;
2630            }
2631        }
2632    }
2633
2634    let vec = Rf_protect(Rf_allocVector(stype, n as isize));
2635    let names_vec = Rf_protect(Rf_allocVector(sexp::STRSXP as c_int, n as isize));
2636    for i in 0..n {
2637        unsafe {
2638            let name_ptr = *names.add(i);
2639            let ch = Rf_mkChar(name_ptr);
2640            let elts = (*names_vec).data as *mut Sexp;
2641            *elts.add(i) = ch;
2642        }
2643    }
2644    Rf_setAttrib(vec, unsafe { R_NamesSymbol }, names_vec);
2645    Rf_unprotect(2);
2646    vec
2647}
2648
2649// Rf_isLanguage — check if LANGSXP
2650#[no_mangle]
2651pub extern "C" fn Rf_isLanguage(x: Sexp) -> c_int {
2652    if x.is_null() {
2653        return 0;
2654    }
2655    (unsafe { (*x).stype } == 6) as c_int // LANGSXP
2656}
2657
2658// R_ExpandFileName — return filename unchanged (no tilde expansion)
2659#[no_mangle]
2660pub extern "C" fn R_ExpandFileName(fn_ptr: *const c_char) -> *const c_char {
2661    fn_ptr
2662}
2663
2664// R_chk_calloc — checked calloc (delegates to system calloc)
2665#[no_mangle]
2666pub extern "C" fn R_chk_calloc(nelem: usize, elsize: usize) -> *mut c_void {
2667    unsafe { calloc(nelem, elsize) as *mut c_void }
2668}
2669
2670// R_chk_realloc — checked realloc (delegates to system realloc)
2671#[no_mangle]
2672pub extern "C" fn R_chk_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
2673    unsafe { realloc(ptr as *mut u8, size) as *mut c_void }
2674}
2675
2676// R_chk_free — checked free (delegates to system free)
2677#[no_mangle]
2678pub extern "C" fn R_chk_free(ptr: *mut c_void) {
2679    unsafe {
2680        free(ptr as *mut u8);
2681    }
2682}
2683
2684// R_removeVarFromFrame — no-op (variable removal not supported from C)
2685#[no_mangle]
2686pub extern "C" fn R_removeVarFromFrame(_sym: Sexp, _env: Sexp) {}
2687
2688// Rf_allocS4Object — stub: allocate a NILSXP
2689#[no_mangle]
2690pub extern "C" fn Rf_allocS4Object() -> Sexp {
2691    Rf_allocVector(sexp::NILSXP as c_int, 0)
2692}
2693
2694// Signal/interrupt stubs
2695#[no_mangle]
2696pub static mut R_interrupts_pending: c_int = 0;
2697#[no_mangle]
2698pub static mut R_interrupts_suspended: c_int = 0;
2699
2700// Event loop stubs (for later/httpuv packages)
2701#[no_mangle]
2702pub static mut R_InputHandlers: *mut c_void = ptr::null_mut();
2703#[no_mangle]
2704pub static mut R_PolledEvents: *mut c_void = ptr::null_mut();
2705#[no_mangle]
2706pub static mut R_wait_usec: c_int = 0;
2707
2708#[no_mangle]
2709pub extern "C" fn addInputHandler(
2710    handlers: *mut c_void,
2711    _fd: c_int,
2712    _action: *mut c_void,
2713    _activity: c_int,
2714) -> *mut c_void {
2715    handlers
2716}
2717
2718#[no_mangle]
2719pub extern "C" fn removeInputHandler(
2720    _handlers: *mut *mut c_void,
2721    _handler: *mut c_void,
2722) -> *mut c_void {
2723    ptr::null_mut()
2724}
2725
2726// ps package stubs — init.c references functions that don't exist in this package version
2727#[no_mangle]
2728pub extern "C" fn ps__list_apps() -> Sexp {
2729    ptr::null_mut()
2730}
2731#[no_mangle]
2732pub extern "C" fn ps__define_errno() -> Sexp {
2733    ptr::null_mut()
2734}
2735
2736// rlang stubs
2737#[no_mangle]
2738pub extern "C" fn R_CheckStack() {}
2739#[no_mangle]
2740pub extern "C" fn R_CheckStack2(_extra: c_int) {}
2741#[no_mangle]
2742pub extern "C" fn R_MakeActiveBinding(_sym: Sexp, _fun: Sexp, _env: Sexp) {}
2743#[no_mangle]
2744pub extern "C" fn R_MakeExternalPtrFn(p: *const (), tag: Sexp, prot: Sexp) -> Sexp {
2745    R_MakeExternalPtr(p as *mut c_void, tag, prot)
2746}
2747#[no_mangle]
2748pub extern "C" fn Rf_allocSExp(stype: c_int) -> Sexp {
2749    Rf_allocVector(stype, 0)
2750}
2751#[no_mangle]
2752pub extern "C" fn Rf_any_duplicated(_x: Sexp, _from_last: c_int) -> isize {
2753    0
2754}
2755#[no_mangle]
2756pub extern "C" fn Rf_countContexts(_type: c_int, _subtype: c_int) -> c_int {
2757    0
2758}
2759#[no_mangle]
2760pub extern "C" fn R_PromiseExpr(_p: Sexp) -> Sexp {
2761    unsafe { R_NilValue }
2762}
2763#[no_mangle]
2764pub extern "C" fn R_ClosureFormals(_x: Sexp) -> Sexp {
2765    unsafe { R_NilValue }
2766}
2767#[no_mangle]
2768pub extern "C" fn R_ClosureBody(_x: Sexp) -> Sexp {
2769    unsafe { R_NilValue }
2770}
2771#[no_mangle]
2772pub extern "C" fn R_ClosureEnv(_x: Sexp) -> Sexp {
2773    unsafe { R_NilValue }
2774}
2775#[no_mangle]
2776pub extern "C" fn R_compute_identical(x: Sexp, y: Sexp, _flags: c_int) -> c_int {
2777    if x == y {
2778        return 1;
2779    }
2780    if x.is_null() || y.is_null() {
2781        return 0;
2782    }
2783    unsafe {
2784        if (*x).stype != (*y).stype {
2785            return 0;
2786        }
2787        if (*x).length != (*y).length {
2788            return 0;
2789        }
2790        let len = (*x).length as usize;
2791        if len == 0 {
2792            return 1;
2793        }
2794        // Compare data bytes directly for numeric/logical/raw/complex types
2795        let elem_size = match (*x).stype {
2796            sexp::REALSXP => 8,
2797            sexp::INTSXP => 4,
2798            sexp::LGLSXP => 4,
2799            sexp::RAWSXP => 1,
2800            sexp::CPLXSXP => 16,
2801            _ => 0,
2802        };
2803        if elem_size > 0 && !(*x).data.is_null() && !(*y).data.is_null() {
2804            let bytes = len * elem_size;
2805            return (std::ptr::eq((*x).data, (*y).data)
2806                || std::slice::from_raw_parts((*x).data, bytes)
2807                    == std::slice::from_raw_parts((*y).data, bytes)) as c_int;
2808        }
2809        // For STRSXP/VECSXP, compare element by element
2810        if (*x).stype == sexp::STRSXP || (*x).stype == sexp::VECSXP {
2811            let ex = (*x).data as *const Sexp;
2812            let ey = (*y).data as *const Sexp;
2813            for i in 0..len {
2814                if R_compute_identical(*ex.add(i), *ey.add(i), _flags) == 0 {
2815                    return 0;
2816                }
2817            }
2818            return 1;
2819        }
2820        // CHARSXP: compare string data
2821        if (*x).stype == sexp::CHARSXP {
2822            if (*x).data.is_null() && (*y).data.is_null() {
2823                return 1;
2824            }
2825            if (*x).data.is_null() || (*y).data.is_null() {
2826                return 0;
2827            }
2828            let sx = CStr::from_ptr((*x).data as *const c_char);
2829            let sy = CStr::from_ptr((*y).data as *const c_char);
2830            return (sx == sy) as c_int;
2831        }
2832        0
2833    }
2834}
2835#[no_mangle]
2836pub extern "C" fn R_envHasNoSpecialSymbols(_env: Sexp) -> c_int {
2837    1
2838}
2839#[no_mangle]
2840pub extern "C" fn R_OrderVector1(
2841    _indx: *mut c_int,
2842    _n: c_int,
2843    _x: Sexp,
2844    _nalast: c_int,
2845    _decreasing: c_int,
2846) {
2847}
2848#[no_mangle]
2849pub extern "C" fn SET_PRENV(_x: Sexp, _v: Sexp) {}
2850#[no_mangle]
2851pub extern "C" fn SET_PRCODE(_x: Sexp, _v: Sexp) {}
2852#[no_mangle]
2853pub extern "C" fn SET_PRVALUE(_x: Sexp, _v: Sexp) {}
2854#[no_mangle]
2855pub extern "C" fn PRCODE(_x: Sexp) -> Sexp {
2856    unsafe { R_NilValue }
2857}
2858#[no_mangle]
2859pub extern "C" fn PRVALUE(_x: Sexp) -> Sexp {
2860    unsafe { R_NilValue }
2861}
2862
2863// Active bindings
2864#[no_mangle]
2865pub extern "C" fn R_BindingIsActive(_sym: Sexp, _env: Sexp) -> c_int {
2866    0
2867}
2868#[no_mangle]
2869pub extern "C" fn R_ActiveBindingFunction(_sym: Sexp, _env: Sexp) -> Sexp {
2870    unsafe { R_NilValue }
2871}
2872#[no_mangle]
2873pub extern "C" fn Rf_onintr() {}
2874
2875// Symbol constants
2876#[no_mangle]
2877pub static mut R_BraceSymbol: Sexp = ptr::null_mut();
2878#[no_mangle]
2879pub static mut R_BracketSymbol: Sexp = ptr::null_mut();
2880#[no_mangle]
2881pub static mut R_Bracket2Symbol: Sexp = ptr::null_mut();
2882#[no_mangle]
2883pub static mut R_DollarSymbol: Sexp = ptr::null_mut();
2884#[no_mangle]
2885pub static mut R_DoubleColonSymbol: Sexp = ptr::null_mut();
2886#[no_mangle]
2887pub static mut R_TripleColonSymbol: Sexp = ptr::null_mut();
2888#[no_mangle]
2889pub static mut R_Interactive: c_int = 0;
2890
2891// Rf_type2str — SEXPTYPE to CHARSXP
2892#[no_mangle]
2893pub extern "C" fn Rf_type2str(stype: c_int) -> Sexp {
2894    Rf_mkChar(Rf_type2char(stype))
2895}
2896
2897// Weak references
2898#[no_mangle]
2899pub extern "C" fn R_MakeWeakRef(key: Sexp, val: Sexp, _fin: Sexp, _onexit: c_int) -> Sexp {
2900    Rf_cons(key, Rf_cons(val, unsafe { R_NilValue }))
2901}
2902#[no_mangle]
2903pub extern "C" fn R_MakeWeakRefC(key: Sexp, val: Sexp, _fin: *const (), onexit: c_int) -> Sexp {
2904    R_MakeWeakRef(key, val, unsafe { R_NilValue }, onexit)
2905}
2906#[no_mangle]
2907pub extern "C" fn R_WeakRefKey(_w: Sexp) -> Sexp {
2908    unsafe { R_NilValue }
2909}
2910#[no_mangle]
2911pub extern "C" fn R_WeakRefValue(_w: Sexp) -> Sexp {
2912    unsafe { R_NilValue }
2913}
2914#[no_mangle]
2915pub extern "C" fn Rf_duplicated(_x: Sexp, _from_last: c_int) -> Sexp {
2916    Rf_allocVector(sexp::LGLSXP as c_int, 0)
2917}
2918#[no_mangle]
2919pub extern "C" fn Rf_any_duplicated3(_x: Sexp, _incomp: Sexp, _from_last: c_int) -> isize {
2920    0
2921}
2922#[no_mangle]
2923pub extern "C" fn Rf_reEnc(
2924    x: *const c_char,
2925    _ce_in: c_int,
2926    _ce_out: c_int,
2927    _subst: c_int,
2928) -> *const c_char {
2929    x
2930}
2931#[no_mangle]
2932pub extern "C" fn Rf_ucstoutf8(buf: *mut c_char, _wc: u32) -> *const c_char {
2933    buf as *const c_char
2934}
2935#[no_mangle]
2936pub extern "C" fn SET_BODY(_x: Sexp, _v: Sexp) {}
2937#[no_mangle]
2938pub extern "C" fn SET_FORMALS(_x: Sexp, _v: Sexp) {}
2939#[no_mangle]
2940pub extern "C" fn SET_CLOENV(_x: Sexp, _v: Sexp) {}
2941#[no_mangle]
2942pub static mut R_NamespaceRegistry: Sexp = ptr::null_mut();
2943#[no_mangle]
2944pub static mut R_Srcref: Sexp = ptr::null_mut();
2945#[no_mangle]
2946pub static mut R_BaseNamespace: Sexp = ptr::null_mut();
2947#[no_mangle]
2948pub extern "C" fn R_EnvironmentIsLocked(_env: Sexp) -> c_int {
2949    0
2950}
2951
2952// Rf_installChar — install symbol from CHARSXP
2953#[no_mangle]
2954pub extern "C" fn Rf_installChar(x: Sexp) -> Sexp {
2955    if x.is_null() {
2956        return unsafe { R_NilValue };
2957    }
2958    let name = unsafe { sexp::char_data(x) };
2959    Rf_install(name.as_ptr() as *const c_char)
2960}
2961// Rf_ScalarRaw — scalar raw vector
2962#[no_mangle]
2963pub extern "C" fn Rf_ScalarRaw(x: u8) -> Sexp {
2964    let s = Rf_allocVector(sexp::RAWSXP as c_int, 1);
2965    unsafe {
2966        *(*s).data = x;
2967    }
2968    s
2969}
2970
2971// Rf_shallow_duplicate — shallow copy (same as duplicate for our purposes)
2972#[no_mangle]
2973pub extern "C" fn Rf_shallow_duplicate(x: Sexp) -> Sexp {
2974    Rf_duplicate(x)
2975}
2976
2977// R_NewEnv — create a C-level environment (ENVSXP).
2978// Uses the SEXP attrib field to store bindings as a pairlist chain.
2979#[no_mangle]
2980pub extern "C" fn R_NewEnv(parent: Sexp, _hash: c_int, _size: c_int) -> Sexp {
2981    unsafe {
2982        let s = calloc(1, std::mem::size_of::<SexpRec>()) as Sexp;
2983        if s.is_null() {
2984            return R_NilValue;
2985        }
2986        (*s).stype = 4; // ENVSXP
2987        (*s).flags = 1; // persistent (survives _minir_free_allocs)
2988        (*s).data = parent as *mut u8; // parent env stored in data
2989        (*s).attrib = R_NilValue; // bindings pairlist
2990        track(s);
2991        s
2992    }
2993}
2994
2995// Rf_defineVar — define a variable via interpreter callback
2996#[no_mangle]
2997pub extern "C" fn Rf_defineVar(sym: Sexp, val: Sexp, env: Sexp) {
2998    if sym.is_null() {
2999        return;
3000    }
3001
3002    // If env is an ENVSXP, try miniR Environment first, then pairlist fallback
3003    if !env.is_null() && unsafe { (*env).stype } == sexp::ENVSXP {
3004        let var_name = unsafe { sexp::char_data(sym) };
3005        if let Some(e) = unsafe { super::convert::env_from_sexp(env) } {
3006            let rval = unsafe { super::convert::sexp_to_rvalue(val) };
3007            e.set(var_name.to_string(), rval);
3008            return;
3009        }
3010        // Pairlist fallback for C-created ENVSXP
3011        let node = Rf_cons(val, unsafe { (*env).attrib });
3012        unsafe {
3013            let pd = (*node).data as *mut sexp::PairlistData;
3014            if !pd.is_null() {
3015                (*pd).tag = sym;
3016            }
3017            (*env).attrib = node;
3018        }
3019        return;
3020    }
3021
3022    // Otherwise use the interpreter callback
3023    let name = unsafe { sexp::char_data(sym) };
3024    if name.is_empty() {
3025        return;
3026    }
3027    let rval = unsafe { super::convert::sexp_to_rvalue(val) };
3028    STATE.with(|state| {
3029        let st = state.borrow();
3030        if let Some(define) = st.callbacks.define_var {
3031            define(name, rval);
3032        }
3033    });
3034}
3035
3036// BODY / CLOENV — closure internals
3037#[no_mangle]
3038pub extern "C" fn BODY(_x: Sexp) -> Sexp {
3039    unsafe { R_NilValue }
3040}
3041
3042#[no_mangle]
3043pub extern "C" fn CLOENV(_x: Sexp) -> Sexp {
3044    unsafe { R_NilValue }
3045}
3046
3047#[no_mangle]
3048pub extern "C" fn FORMALS(_x: Sexp) -> Sexp {
3049    unsafe { R_NilValue }
3050}
3051
3052// Rf_isObject — check if object has a class attribute
3053#[no_mangle]
3054pub extern "C" fn Rf_isObject(x: Sexp) -> c_int {
3055    if x.is_null() {
3056        return 0;
3057    }
3058    let klass = Rf_getAttrib(x, unsafe { R_ClassSymbol });
3059    (!klass.is_null() && unsafe { (*klass).stype } == sexp::STRSXP) as c_int
3060}
3061
3062// Rf_str2type — string to SEXPTYPE
3063#[no_mangle]
3064pub extern "C" fn Rf_str2type(s: *const c_char) -> c_int {
3065    if s.is_null() {
3066        return -1;
3067    }
3068    let name = unsafe { CStr::from_ptr(s) }.to_str().unwrap_or("");
3069    match name {
3070        "NULL" => sexp::NILSXP as c_int,
3071        "logical" => sexp::LGLSXP as c_int,
3072        "integer" => sexp::INTSXP as c_int,
3073        "double" | "numeric" => sexp::REALSXP as c_int,
3074        "complex" => sexp::CPLXSXP as c_int,
3075        "character" => sexp::STRSXP as c_int,
3076        "list" => sexp::VECSXP as c_int,
3077        "raw" => sexp::RAWSXP as c_int,
3078        _ => -1,
3079    }
3080}
3081
3082#[repr(C)]
3083pub struct Rcomplex {
3084    r: f64,
3085    i: f64,
3086}
3087
3088// Rf_ScalarComplex
3089#[no_mangle]
3090pub extern "C" fn Rf_ScalarComplex(c: Rcomplex) -> Sexp {
3091    let s = Rf_allocVector(sexp::CPLXSXP as c_int, 1);
3092    unsafe {
3093        let ptr = (*s).data as *mut Rcomplex;
3094        *ptr = c;
3095    }
3096    s
3097}
3098
3099// Environment internals
3100#[no_mangle]
3101pub extern "C" fn ENCLOS(_x: Sexp) -> Sexp {
3102    unsafe { R_NilValue }
3103}
3104#[no_mangle]
3105pub extern "C" fn R_existsVarInFrame(_env: Sexp, _sym: Sexp) -> c_int {
3106    0
3107}
3108#[no_mangle]
3109pub extern "C" fn R_IsNamespaceEnv(env: Sexp) -> c_int {
3110    if let Some(e) = unsafe { super::convert::env_from_sexp(env) } {
3111        if let Some(name) = e.name() {
3112            if name.starts_with("namespace:") {
3113                return 1;
3114            }
3115        }
3116    }
3117    0
3118}
3119#[no_mangle]
3120pub extern "C" fn R_lsInternal3(_env: Sexp, _all: c_int, _sorted: c_int) -> Sexp {
3121    Rf_allocVector(sexp::STRSXP as c_int, 0)
3122}
3123#[no_mangle]
3124pub extern "C" fn R_ClosureExpr(_x: Sexp) -> Sexp {
3125    unsafe { R_NilValue }
3126}
3127#[no_mangle]
3128pub extern "C" fn R_ParentEnv(_env: Sexp) -> Sexp {
3129    unsafe { R_NilValue }
3130}
3131#[no_mangle]
3132pub extern "C" fn R_LockBinding(_sym: Sexp, _env: Sexp) {}
3133#[no_mangle]
3134pub extern "C" fn SET_FRAME(_x: Sexp, _v: Sexp) {}
3135#[no_mangle]
3136pub extern "C" fn SET_ENCLOS(_x: Sexp, _v: Sexp) {}
3137#[no_mangle]
3138pub extern "C" fn SET_HASHTAB(_x: Sexp, _v: Sexp) {}
3139#[no_mangle]
3140pub extern "C" fn R_BindingIsLocked(_sym: Sexp, _env: Sexp) -> c_int {
3141    0
3142}
3143#[no_mangle]
3144pub extern "C" fn R_NamespaceEnvSpec(_ns: Sexp) -> Sexp {
3145    unsafe { R_NilValue }
3146}
3147#[no_mangle]
3148pub extern "C" fn R_FindNamespace(_name: Sexp) -> Sexp {
3149    unsafe { R_NilValue }
3150}
3151#[no_mangle]
3152pub extern "C" fn R_IsPackageEnv(_env: Sexp) -> c_int {
3153    0
3154}
3155#[no_mangle]
3156pub extern "C" fn R_PackageEnvName(_env: Sexp) -> Sexp {
3157    unsafe { R_NilValue }
3158}
3159
3160// Rf_findVarInFrame — variable lookup (stub)
3161#[no_mangle]
3162pub extern "C" fn Rf_findVarInFrame(env: Sexp, sym: Sexp) -> Sexp {
3163    Rf_findVarInFrame3(env, sym, 1)
3164}
3165
3166// Rf_GetOption1 — get option value (stub)
3167#[no_mangle]
3168pub extern "C" fn Rf_GetOption1(_tag: Sexp) -> Sexp {
3169    unsafe { R_NilValue }
3170}
3171
3172// Rf_GetOptionWidth — return the current console width option
3173#[no_mangle]
3174pub extern "C" fn Rf_GetOptionWidth() -> c_int {
3175    80 // default R console width
3176}
3177
3178// S_realloc — reallocate and zero-fill new portion
3179#[no_mangle]
3180pub extern "C" fn S_realloc(
3181    ptr: *mut c_char,
3182    new_size: isize,
3183    old_size: isize,
3184    elt_size: c_int,
3185) -> *mut c_char {
3186    extern "C" {
3187        fn realloc(ptr: *mut u8, size: usize) -> *mut u8;
3188    }
3189    let new_bytes = new_size as usize * elt_size as usize;
3190    let old_bytes = old_size as usize * elt_size as usize;
3191    unsafe {
3192        let new_ptr = realloc(ptr as *mut u8, new_bytes);
3193        if !new_ptr.is_null() && new_bytes > old_bytes {
3194            ptr::write_bytes(new_ptr.add(old_bytes), 0, new_bytes - old_bytes);
3195        }
3196        new_ptr as *mut c_char
3197    }
3198}
3199
3200// norm_rand — Box-Muller transform using unif_rand()
3201#[no_mangle]
3202pub extern "C" fn norm_rand() -> f64 {
3203    let u1 = unif_rand();
3204    let u2 = unif_rand();
3205    (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
3206}
3207
3208// exp_rand — exponential deviate using unif_rand()
3209#[no_mangle]
3210pub extern "C" fn exp_rand() -> f64 {
3211    -unif_rand().ln()
3212}
3213
3214// R_ParseVector — parse R source text via interpreter callback.
3215#[no_mangle]
3216pub extern "C" fn R_ParseVector(
3217    text: Sexp,
3218    _n: c_int,
3219    parse_status: *mut c_int,
3220    _srcfile: Sexp,
3221) -> Sexp {
3222    // Extract the text string from the SEXP
3223    let src = if text.is_null() {
3224        String::new()
3225    } else {
3226        unsafe {
3227            if (*text).stype == sexp::STRSXP && (*text).length > 0 {
3228                let elt = *((*text).data as *const Sexp);
3229                if !elt.is_null() {
3230                    sexp::char_data(elt).to_string()
3231                } else {
3232                    String::new()
3233                }
3234            } else if (*text).stype == sexp::CHARSXP {
3235                sexp::char_data(text).to_string()
3236            } else {
3237                String::new()
3238            }
3239        }
3240    };
3241
3242    if src.is_empty() {
3243        if !parse_status.is_null() {
3244            unsafe {
3245                *parse_status = 1;
3246            } // PARSE_OK
3247        }
3248        return unsafe { R_NilValue };
3249    }
3250
3251    // Try the parse callback
3252    let result = STATE.with(|state| {
3253        let st = state.borrow();
3254        st.callbacks.parse_text.map(|parse_fn| parse_fn(&src))
3255    });
3256
3257    match result {
3258        Some(Ok(val)) => {
3259            if !parse_status.is_null() {
3260                unsafe {
3261                    *parse_status = 1;
3262                } // PARSE_OK
3263            }
3264            // Stash the parsed RValue so Rf_eval can retrieve it.
3265            // Create a LANGSXP with the stash index encoded in the data pointer.
3266            let idx = stash_rvalue(val);
3267            let expr_sexp = sexp::alloc_vector(sexp::LANGSXP, 0);
3268            unsafe {
3269                // Mark as stashed LANGSXP with length=-1 sentinel
3270                (*expr_sexp).length = -1;
3271                (*expr_sexp).data = idx as *mut u8;
3272            }
3273            track(expr_sexp);
3274            // Wrap in a length-1 VECSXP — r_parse() extracts element 0
3275            let list = Rf_allocVector(sexp::VECSXP as c_int, 1);
3276            unsafe {
3277                let elts = (*list).data as *mut Sexp;
3278                *elts = expr_sexp;
3279            }
3280            list
3281        }
3282        Some(Err(_)) => {
3283            if !parse_status.is_null() {
3284                unsafe {
3285                    *parse_status = 3;
3286                } // PARSE_ERROR
3287            }
3288            unsafe { R_NilValue }
3289        }
3290        None => {
3291            // No callback — return NilValue but mark as OK
3292            if !parse_status.is_null() {
3293                unsafe {
3294                    *parse_status = 1;
3295                }
3296            }
3297            unsafe { R_NilValue }
3298        }
3299    }
3300}
3301
3302// Fortran optimization stubs (called by MASS, nnet, class, etc.)
3303#[no_mangle]
3304pub extern "C" fn nmmin(
3305    _n: c_int,
3306    _xin: *mut f64,
3307    _x: *mut f64,
3308    _fmin: *mut f64,
3309    _fn_ptr: *const (),
3310    _fail: *mut c_int,
3311    _abstol: f64,
3312    _intol: f64,
3313    _ex: *mut c_void,
3314    _alpha: f64,
3315    _beta: f64,
3316    _gamma: f64,
3317    _trace: c_int,
3318    _fncount: *mut c_int,
3319    _maxit: c_int,
3320) {
3321    let _ = std::io::Write::write_all(
3322        &mut std::io::stderr(),
3323        b"Warning: nmmin() is a stub in miniR -- results will be incorrect\n",
3324    );
3325}
3326
3327#[no_mangle]
3328pub extern "C" fn vmmin(
3329    _n: c_int,
3330    _x: *mut f64,
3331    _fmin: *mut f64,
3332    _fn_ptr: *const (),
3333    _gr: *const (),
3334    _maxit: c_int,
3335    _trace: c_int,
3336    _mask: *mut c_int,
3337    _abstol: f64,
3338    _reltol: f64,
3339    _nreport: c_int,
3340    _ex: *mut c_void,
3341    _fncount: *mut c_int,
3342    _grcount: *mut c_int,
3343    _fail: *mut c_int,
3344) {
3345    let _ = std::io::Write::write_all(
3346        &mut std::io::stderr(),
3347        b"Warning: vmmin() is a stub in miniR -- results will be incorrect\n",
3348    );
3349}
3350
3351// region: External pointer setters
3352
3353#[no_mangle]
3354pub extern "C" fn R_SetExternalPtrTag(s: Sexp, _tag: Sexp) {
3355    // No-op stub — miniR external pointers don't store tag/prot in the SEXP
3356    let _ = s;
3357}
3358
3359#[no_mangle]
3360pub extern "C" fn R_SetExternalPtrProtected(s: Sexp, _prot: Sexp) {
3361    let _ = s;
3362}
3363
3364// endregion
3365
3366// region: S4 object creation
3367
3368#[no_mangle]
3369pub extern "C" fn R_do_new_object(_class_def: Sexp) -> Sexp {
3370    // Stub -- allocate an empty S4-like object (VECSXP with class attribute)
3371    Rf_allocVector(sexp::VECSXP as c_int, 0)
3372}
3373
3374#[no_mangle]
3375pub extern "C" fn R_getClassDef(_what: *const c_char) -> Sexp {
3376    // Stub -- return R_NilValue (class not found)
3377    unsafe { R_NilValue }
3378}
3379
3380// endregion
3381
3382// region: Rmath — rounding, gamma, and special functions kept here;
3383// all distribution functions (d/p/q/r) moved to rmath.rs
3384
3385// Rounding and truncation
3386#[no_mangle]
3387pub extern "C" fn Rf_fround(x: f64, digits: f64) -> f64 {
3388    let m = 10.0_f64.powf(digits);
3389    (x * m).round() / m
3390}
3391
3392#[no_mangle]
3393pub extern "C" fn Rf_ftrunc(x: f64) -> f64 {
3394    x.trunc()
3395}
3396
3397#[no_mangle]
3398pub extern "C" fn Rf_fprec(x: f64, digits: f64) -> f64 {
3399    if x == 0.0 || !x.is_finite() || digits <= 0.0 {
3400        return x;
3401    }
3402    let digits = digits as i32;
3403    let magnitude = x.abs().log10().floor() as i32 + 1;
3404    let scale = 10.0_f64.powi(digits - magnitude);
3405    (x * scale).round() / scale
3406}
3407
3408#[no_mangle]
3409pub extern "C" fn Rf_sign(x: f64) -> f64 {
3410    if x > 0.0 {
3411        1.0
3412    } else if x < 0.0 {
3413        -1.0
3414    } else {
3415        0.0
3416    }
3417}
3418
3419// Gamma and related functions
3420#[no_mangle]
3421pub extern "C" fn Rf_gammafn(x: f64) -> f64 {
3422    libm::tgamma(x)
3423}
3424
3425#[no_mangle]
3426pub extern "C" fn Rf_lgammafn(x: f64) -> f64 {
3427    libm::lgamma(x)
3428}
3429
3430#[no_mangle]
3431pub extern "C" fn Rf_lgammafn_sign(x: f64, sgn: *mut c_int) -> f64 {
3432    if !sgn.is_null() {
3433        unsafe {
3434            *sgn = if x >= 0.0 { 1 } else { -1 };
3435        }
3436    }
3437    libm::lgamma(x)
3438}
3439
3440#[no_mangle]
3441pub extern "C" fn Rf_digamma(x: f64) -> f64 {
3442    super::rmath::digamma_fn(x)
3443}
3444
3445#[no_mangle]
3446pub extern "C" fn Rf_trigamma(x: f64) -> f64 {
3447    super::rmath::trigamma(x)
3448}
3449
3450#[no_mangle]
3451pub extern "C" fn Rf_tetragamma(x: f64) -> f64 {
3452    super::rmath::tetragamma(x)
3453}
3454
3455#[no_mangle]
3456pub extern "C" fn Rf_pentagamma(x: f64) -> f64 {
3457    super::rmath::pentagamma(x)
3458}
3459
3460#[no_mangle]
3461pub extern "C" fn Rf_psigamma(x: f64, deriv: f64) -> f64 {
3462    super::rmath::psigamma_fn(x, deriv)
3463}
3464
3465#[no_mangle]
3466pub extern "C" fn Rf_beta(a: f64, b: f64) -> f64 {
3467    super::rmath::beta_fn(a, b)
3468}
3469
3470#[no_mangle]
3471pub extern "C" fn Rf_lbeta(a: f64, b: f64) -> f64 {
3472    super::rmath::lbeta_fn(a, b)
3473}
3474
3475#[no_mangle]
3476pub extern "C" fn Rf_choose(n: f64, k: f64) -> f64 {
3477    super::rmath::choose_fn(n, k)
3478}
3479
3480#[no_mangle]
3481pub extern "C" fn Rf_lchoose(n: f64, k: f64) -> f64 {
3482    super::rmath::lchoose_fn(n, k)
3483}
3484
3485#[no_mangle]
3486pub extern "C" fn Rf_log1pmx(x: f64) -> f64 {
3487    super::rmath::log1pmx_fn(x)
3488}
3489
3490#[no_mangle]
3491pub extern "C" fn Rf_lgamma1p(a: f64) -> f64 {
3492    super::rmath::lgamma1p_fn(a)
3493}
3494
3495#[no_mangle]
3496pub extern "C" fn Rf_logspace_add(lx: f64, ly: f64) -> f64 {
3497    super::rmath::logspace_add_fn(lx, ly)
3498}
3499
3500#[no_mangle]
3501pub extern "C" fn Rf_logspace_sub(lx: f64, ly: f64) -> f64 {
3502    super::rmath::logspace_sub_fn(lx, ly)
3503}
3504
3505#[no_mangle]
3506pub extern "C" fn log1pexp(x: f64) -> f64 {
3507    if x <= -37.0 {
3508        x.exp()
3509    } else if x <= 18.0 {
3510        (1.0 + x.exp()).ln()
3511    } else {
3512        x + (-x).exp()
3513    }
3514}
3515
3516#[no_mangle]
3517pub extern "C" fn Rf_dpsifn(
3518    x: f64,
3519    n: c_int,
3520    _kode: c_int,
3521    _m: c_int,
3522    ans: *mut f64,
3523    nz: *mut c_int,
3524    ierr: *mut c_int,
3525) {
3526    if !ans.is_null() {
3527        unsafe {
3528            *ans = super::rmath::psigamma_fn(x, n as f64);
3529        }
3530    }
3531    if !nz.is_null() {
3532        unsafe {
3533            *nz = 0;
3534        }
3535    }
3536    if !ierr.is_null() {
3537        unsafe {
3538            *ierr = 0;
3539        }
3540    }
3541}
3542
3543// endregion
3544
3545// Rf_isS4 — check if object is S4
3546#[no_mangle]
3547pub extern "C" fn Rf_isS4(x: Sexp) -> c_int {
3548    if x.is_null() {
3549        return 0;
3550    }
3551    // OBJSXP = 25
3552    let stype = unsafe { (*x).stype };
3553    if stype == 25 {
3554        return 1;
3555    }
3556    Rf_inherits(x, c"refClass".as_ptr())
3557}
3558
3559// R_has_slot — check if S4 object has a named slot
3560#[no_mangle]
3561pub extern "C" fn R_has_slot(obj: Sexp, name: Sexp) -> c_int {
3562    if obj.is_null() || name.is_null() {
3563        return 0;
3564    }
3565    let attr = Rf_getAttrib(obj, name);
3566    (!attr.is_null() && attr != unsafe { R_NilValue }) as c_int
3567}
3568
3569// R_MakeUnwindCont — create unwind continuation token (stub)
3570#[no_mangle]
3571pub extern "C" fn R_MakeUnwindCont() -> Sexp {
3572    // Return a simple VECSXP as a token — miniR does not use longjmp unwind
3573    Rf_allocVector(sexp::VECSXP as c_int, 0)
3574}
3575
3576// R_ContinueUnwind — continue an unwind (stub — no-op)
3577#[no_mangle]
3578pub extern "C" fn R_ContinueUnwind(_cont: Sexp) {
3579    // In miniR, unwind protection is a no-op
3580}
3581
3582// R_UnwindProtect — execute with unwind protection (stub)
3583#[no_mangle]
3584pub extern "C" fn R_UnwindProtect(
3585    fun: Option<extern "C" fn(*mut c_void) -> Sexp>,
3586    data: *mut c_void,
3587    _cleanfun: Option<extern "C" fn(*mut c_void, c_int)>,
3588    _cleandata: *mut c_void,
3589    _cont: Sexp,
3590) -> Sexp {
3591    // Just call the function directly — no unwind protection in miniR
3592    match fun {
3593        Some(f) => f(data),
3594        None => unsafe { R_NilValue },
3595    }
3596}
3597
3598// endregion
3599
3600// region: Cleanup
3601
3602/// Free all tracked allocations (called by Rust after .Call).
3603/// Persistent SEXPs (external pointers, flags=1) are kept alive.
3604pub fn free_allocs() {
3605    // Temporarily leak all SEXP allocations — C code may store pointers
3606    // to tracked SEXPs in static variables (e.g. magrittr's new_env_call).
3607    // Freeing them causes use-after-free crashes.
3608    // TODO: implement proper SEXP lifetime management (reference counting or GC)
3609    STATE.with(|state| {
3610        let mut st = state.borrow_mut();
3611        st.alloc_head = ptr::null_mut();
3612        st.protect_stack.clear();
3613    });
3614}
3615
3616// endregion