Skip to main content

r/interpreter/builtins/
stubs.rs

1//! Stub builtins — not-yet-implemented functions that return sensible defaults
2//! or fail explicitly. Also includes lightweight implementations of commonly
3//! needed functions that don't warrant their own module.
4
5#[cfg(not(feature = "native"))]
6use tracing::debug;
7
8use crate::interpreter::value::*;
9use crate::interpreter::BuiltinContext;
10use minir_macros::{builtin, interpreter_builtin, stub_builtin};
11
12// region: Package management stubs
13
14stub_builtin!("installed.packages");
15stub_builtin!("install.packages");
16
17// endregion
18
19// region: C-level interface stubs
20
21#[cfg(not(feature = "native"))]
22/// .Call — stub for C-level function calls. Returns NULL with a warning.
23/// Many CRAN packages use .Call for compiled code we can't execute.
24///
25/// @param .NAME external function reference
26/// @param ... arguments passed to C
27/// @return NULL
28/// @namespace base
29#[builtin(name = ".Call")]
30fn builtin_dot_call(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
31    let name = args
32        .first()
33        .and_then(|v| v.as_vector()?.as_character_scalar())
34        .unwrap_or_else(|| "<native>".to_string());
35    debug!(
36        symbol = name.as_str(),
37        nargs = args.len().saturating_sub(1),
38        "native .Call"
39    );
40    Err(RError::new(
41        RErrorKind::Other,
42        format!(".Call(\"{name}\") is not available — miniR cannot call compiled C/C++ code"),
43    ))
44}
45
46#[cfg(not(feature = "native"))]
47/// .C — stub for .C calling convention. Not available without native feature.
48///
49/// @param .NAME external function reference
50/// @param ... arguments passed to C
51/// @return error
52/// @namespace base
53#[builtin(name = ".C")]
54fn builtin_dot_c(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
55    let name = args
56        .first()
57        .and_then(|v| v.as_vector()?.as_character_scalar())
58        .unwrap_or_else(|| "<native>".to_string());
59    debug!(
60        symbol = name.as_str(),
61        nargs = args.len().saturating_sub(1),
62        "native .C"
63    );
64    Err(RError::new(
65        RErrorKind::Other,
66        format!(".C(\"{name}\") is not available — miniR cannot call compiled C/C++ code"),
67    ))
68}
69
70/// .Internal — stub for R internal functions.
71///
72/// @param call the internal function call
73/// @return error
74/// @namespace base
75#[builtin(name = ".Internal")]
76fn builtin_dot_internal(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
77    Err(RError::new(
78        RErrorKind::Other,
79        ".Internal() is not available in miniR".to_string(),
80    ))
81}
82
83#[cfg(not(feature = "native"))]
84/// .External — stub for external C calls.
85///
86/// @param .NAME external function reference
87/// @param ... arguments
88/// @return error
89/// @namespace base
90#[builtin(name = ".External")]
91fn builtin_dot_external(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
92    Err(RError::new(
93        RErrorKind::Other,
94        ".External() is not available — miniR was built without the 'native' feature".to_string(),
95    ))
96}
97
98// .External2 is implemented in native_code.rs when the native feature is enabled.
99// Without native, it returns an error.
100#[cfg(not(feature = "native"))]
101/// .External2 — stub when native feature is disabled.
102#[builtin(name = ".External2")]
103fn builtin_dot_external2(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
104    Err(RError::new(
105        RErrorKind::Other,
106        ".External2() is not available — miniR was built without the 'native' feature".to_string(),
107    ))
108}
109
110// endregion
111
112// region: tools package stubs
113
114/// tools::vignetteEngine — register/query vignette engines.
115/// `removeSource(fn)` — strip source references from a function.
116///
117/// No-op in miniR — we don't store source references.
118///
119/// @param fn a function
120/// @return the function unchanged
121/// @namespace utils
122#[builtin(name = "removeSource", min_args = 1)]
123fn builtin_remove_source(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
124    Ok(args[0].clone())
125}
126
127/// No-op in miniR — vignette processing is not supported.
128/// When called with `package=` to query registered engines, returns an empty
129/// list so callers can iterate without error.
130///
131/// @param name engine name (or missing to register)
132/// @param ... ignored
133/// @return empty list (query mode) or NULL (registration mode)
134/// @namespace tools
135#[builtin(name = "vignetteEngine")]
136fn builtin_vignette_engine(_args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
137    // When called with package= arg, return an empty list (query mode)
138    if named.iter().any(|(n, _)| n == "package") {
139        return Ok(RValue::List(RList::new(vec![])));
140    }
141    Ok(RValue::Null)
142}
143
144// endregion
145
146// region: S4 class lookup
147
148/// Look up an S4 class definition by name.
149///
150/// @param Class character string naming the class
151/// @param where environment to search in (ignored)
152/// @return the class definition or NULL
153/// @namespace methods
154#[builtin(name = "getClassDef", namespace = "methods")]
155fn builtin_get_class_def(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
156    let _name = args
157        .first()
158        .and_then(|v| v.as_vector()?.as_character_scalar())
159        .unwrap_or_default();
160    // Return NULL — we don't have a global class definition registry accessible here.
161    // The per-interpreter S4 registry is on the Interpreter struct, not accessible
162    // from a plain builtin. This is enough to not crash packages that call getClassDef.
163    Ok(RValue::Null)
164}
165
166/// Check if a method exists for an S4 generic.
167///
168/// @param f character: generic function name
169/// @param signature character: method signature
170/// @return logical
171/// @namespace methods
172#[interpreter_builtin(name = "hasMethod", namespace = "methods")]
173fn interp_has_method(
174    _args: &[RValue],
175    _named: &[(String, RValue)],
176    context: &BuiltinContext,
177) -> Result<RValue, RError> {
178    context
179        .interpreter()
180        .write_stderr("[miniR stub] hasMethod() is a no-op in miniR — always returns FALSE\n");
181    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
182}
183
184/// Set old-style S3 class for S4 compatibility.
185///
186/// @param Classes character vector of class names
187/// @return invisible NULL
188/// @namespace methods
189#[interpreter_builtin(name = "setOldClass", namespace = "methods")]
190fn interp_set_old_class(
191    _args: &[RValue],
192    _named: &[(String, RValue)],
193    _context: &BuiltinContext,
194) -> Result<RValue, RError> {
195    Ok(RValue::Null)
196}
197
198// endregion
199
200// region: Common utilities that packages expect
201
202/// .Deprecated — warn that a function is deprecated.
203///
204/// @param new replacement function name
205/// @param package package name
206/// @param msg custom message
207/// @namespace base
208#[builtin(name = ".Deprecated")]
209fn builtin_deprecated(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
210    let new = args
211        .first()
212        .and_then(|v| v.as_vector()?.as_character_scalar())
213        .unwrap_or_default();
214    if !new.is_empty() {
215        // Just warn, don't error
216        return Ok(RValue::Null);
217    }
218    Ok(RValue::Null)
219}
220
221/// .Defunct — error that a function is defunct.
222///
223/// @param new replacement function name
224/// @param package package name
225/// @param msg custom message
226/// @namespace base
227#[builtin(name = ".Defunct")]
228fn builtin_defunct(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
229    let msg = args
230        .first()
231        .and_then(|v| v.as_vector()?.as_character_scalar())
232        .unwrap_or_else(|| "this function is defunct".to_string());
233    Err(RError::other(msg))
234}
235
236/// packageStartupMessage — like message() but suppressable by suppressPackageStartupMessages.
237///
238/// @param ... message parts
239/// @namespace base
240#[builtin(name = "packageStartupMessage")]
241fn builtin_package_startup_message(
242    _args: &[RValue],
243    _: &[(String, RValue)],
244) -> Result<RValue, RError> {
245    // Silently ignored — startup messages are informational only
246    Ok(RValue::Null)
247}
248
249/// suppressPackageStartupMessages — suppress package startup messages.
250///
251/// @param expr expression to evaluate
252/// @return result of expr
253/// @namespace base
254#[builtin(name = "suppressPackageStartupMessages", min_args = 1)]
255fn builtin_suppress_pkg_startup(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
256    Ok(args.first().cloned().unwrap_or(RValue::Null))
257}
258
259/// is.R — check if running in R (always TRUE for miniR).
260///
261/// @return TRUE
262/// @namespace base
263#[builtin(name = "is.R")]
264fn builtin_is_r(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
265    Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
266}
267
268/// getRversion — return the R version as a character string.
269///
270/// @return character scalar version string
271/// @namespace base
272#[builtin(name = "getRversion")]
273fn builtin_get_rversion(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
274    Ok(RValue::vec(Vector::Character(
275        vec![Some("4.4.0".to_string())].into(),
276    )))
277}
278
279/// numeric_version — create a version object (returns as character for now).
280///
281/// @param x character string version
282/// @return version object (character scalar)
283/// @namespace base
284#[builtin(name = "numeric_version", min_args = 1)]
285fn builtin_numeric_version(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
286    // Return as-is — version comparison not implemented but the object exists
287    Ok(args.first().cloned().unwrap_or(RValue::Null))
288}
289
290/// package_version — create a package version object.
291///
292/// @param x character string version
293/// @return version object
294/// @namespace base
295#[builtin(name = "package_version", min_args = 1)]
296fn builtin_package_version(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
297    Ok(args.first().cloned().unwrap_or(RValue::Null))
298}
299
300/// OlsonNames — return timezone names.
301///
302/// @return character vector of timezone names
303/// @namespace base
304#[builtin(name = "OlsonNames")]
305fn builtin_olson_names(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
306    Ok(RValue::vec(Vector::Character(
307        vec![
308            Some("UTC".to_string()),
309            Some("GMT".to_string()),
310            Some("US/Eastern".to_string()),
311            Some("US/Central".to_string()),
312            Some("US/Mountain".to_string()),
313            Some("US/Pacific".to_string()),
314            Some("Europe/London".to_string()),
315            Some("Europe/Berlin".to_string()),
316            Some("Europe/Paris".to_string()),
317            Some("Asia/Tokyo".to_string()),
318            Some("Australia/Sydney".to_string()),
319        ]
320        .into(),
321    )))
322}
323
324// endregion
325
326// region: Namespace utilities
327
328/// Get a namespace environment by name (string → environment).
329///
330/// Delegates to the same logic as `getNamespace()`: checks loaded namespaces,
331/// attempts to load unloaded namespaces, and falls back to the base environment
332/// for built-in namespaces.
333///
334/// @param ns character scalar: namespace name
335/// @return environment
336/// @namespace base
337#[interpreter_builtin(name = "asNamespace", min_args = 1)]
338fn interp_as_namespace(
339    args: &[RValue],
340    _named: &[(String, RValue)],
341    context: &BuiltinContext,
342) -> Result<RValue, RError> {
343    let ns = args
344        .first()
345        .and_then(|v| v.as_vector()?.as_character_scalar())
346        .ok_or_else(|| RError::new(RErrorKind::Argument, "invalid namespace name".to_string()))?;
347
348    // Check loaded packages first
349    let loaded_ns = context.with_interpreter(|interp| {
350        interp
351            .loaded_namespaces
352            .borrow()
353            .get(&ns)
354            .map(|loaded| loaded.namespace_env.clone())
355    });
356
357    if let Some(env) = loaded_ns {
358        return Ok(RValue::Environment(env));
359    }
360
361    // Try to load the namespace if it's not already loaded
362    let loaded_env = context.with_interpreter(|interp| interp.load_namespace(&ns).ok());
363
364    if let Some(env) = loaded_env {
365        return Ok(RValue::Environment(env));
366    }
367
368    // Fall back to base env for builtin namespaces (base, utils, stats, etc.)
369    let env = context.with_interpreter(|interp| interp.base_env());
370    Ok(RValue::Environment(env))
371}
372
373/// Get the name of a namespace environment.
374///
375/// @param ns namespace environment
376/// @return character scalar
377/// @namespace base
378#[builtin(name = "getNamespaceName", min_args = 1)]
379fn builtin_get_namespace_name(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
380    match args.first() {
381        Some(RValue::Environment(env)) => {
382            let name = env.name().unwrap_or_default();
383            Ok(RValue::vec(Vector::Character(vec![Some(name)].into())))
384        }
385        _ => Ok(RValue::vec(Vector::Character(
386            vec![Some(String::new())].into(),
387        ))),
388    }
389}
390
391/// Check if an object is a namespace environment.
392///
393/// Returns TRUE if the object is an environment whose name starts with
394/// "namespace:" (indicating it was created by the package loader), or if it
395/// matches one of the loaded namespace environments in the interpreter's
396/// registry.
397///
398/// @param ns object to check
399/// @return logical
400/// @namespace base
401#[interpreter_builtin(name = "isNamespace", min_args = 1)]
402fn interp_is_namespace(
403    args: &[RValue],
404    _named: &[(String, RValue)],
405    context: &BuiltinContext,
406) -> Result<RValue, RError> {
407    let is_ns = match args.first() {
408        Some(RValue::Environment(env)) => {
409            // Check if the environment has a "namespace:" prefix in its name
410            let has_ns_name = env
411                .name()
412                .map(|n| n.starts_with("namespace:"))
413                .unwrap_or(false);
414
415            if has_ns_name {
416                true
417            } else {
418                // Check if the environment is in the loaded_namespaces registry
419                context.with_interpreter(|interp| {
420                    interp
421                        .loaded_namespaces
422                        .borrow()
423                        .values()
424                        .any(|loaded| loaded.namespace_env.ptr_eq(env))
425                })
426            }
427        }
428        _ => false,
429    };
430    Ok(RValue::vec(Vector::Logical(vec![Some(is_ns)].into())))
431}
432
433/// Get the top-level environment.
434///
435/// @param envir starting environment
436/// @return the top-level environment (global or namespace)
437/// @namespace base
438#[builtin(name = "topenv", min_args = 0)]
439fn builtin_topenv(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
440    match args.first() {
441        Some(RValue::Environment(env)) => {
442            // Walk up the parent chain to find a namespace, global, or base env
443            let mut current = env.clone();
444            loop {
445                if let Some(name) = current.name() {
446                    if name.starts_with("namespace:")
447                        || name == "R_GlobalEnv"
448                        || name == "base"
449                        || name.starts_with("package:")
450                    {
451                        return Ok(RValue::Environment(current));
452                    }
453                }
454                match current.parent() {
455                    Some(parent) => current = parent,
456                    None => return Ok(RValue::Environment(current)),
457                }
458            }
459        }
460        _ => Ok(RValue::Null),
461    }
462}
463
464// endregion
465
466// region: Date/time constructors
467
468/// .POSIXct — construct a POSIXct object from numeric seconds.
469///
470/// @param xx numeric: seconds since epoch
471/// @param tz character: timezone (default "")
472/// @return POSIXct object
473/// @namespace base
474#[builtin(name = ".POSIXct", min_args = 1)]
475fn builtin_dot_posixct(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
476    let val = args.first().cloned().unwrap_or(RValue::Null);
477    match val {
478        RValue::Vector(mut rv) => {
479            rv.set_attr(
480                "class".to_string(),
481                RValue::vec(Vector::Character(
482                    vec![Some("POSIXct".to_string()), Some("POSIXt".to_string())].into(),
483                )),
484            );
485            Ok(RValue::Vector(rv))
486        }
487        _ => Ok(val),
488    }
489}
490
491/// .POSIXlt — construct a POSIXlt object (list-based time).
492///
493/// @param xx numeric or list
494/// @param tz character: timezone
495/// @return POSIXlt object
496/// @namespace base
497#[builtin(name = ".POSIXlt", min_args = 1)]
498fn builtin_dot_posixlt(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
499    let val = args.first().cloned().unwrap_or(RValue::Null);
500    // Just tag with class — full POSIXlt structure not implemented
501    match val {
502        RValue::List(mut list) => {
503            let mut attrs = *list.attrs.take().unwrap_or_default();
504            attrs.insert(
505                "class".to_string(),
506                RValue::vec(Vector::Character(
507                    vec![Some("POSIXlt".to_string()), Some("POSIXt".to_string())].into(),
508                )),
509            );
510            list.attrs = Some(Box::new(attrs));
511            Ok(RValue::List(list))
512        }
513        _ => Ok(val),
514    }
515}
516
517/// .Date — construct a Date object from numeric days since epoch.
518///
519/// @param xx numeric: days since 1970-01-01
520/// @return Date object
521/// @namespace base
522#[builtin(name = ".Date", min_args = 1)]
523fn builtin_dot_date(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
524    let val = args.first().cloned().unwrap_or(RValue::Null);
525    match val {
526        RValue::Vector(mut rv) => {
527            rv.set_attr(
528                "class".to_string(),
529                RValue::vec(Vector::Character(vec![Some("Date".to_string())].into())),
530            );
531            Ok(RValue::Vector(rv))
532        }
533        _ => Ok(val),
534    }
535}
536
537/// .difftime — construct a difftime object.
538///
539/// @param xx numeric value
540/// @param units character: time units
541/// @return difftime object
542/// @namespace base
543#[builtin(name = ".difftime", min_args = 1)]
544fn builtin_dot_difftime(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
545    let val = args.first().cloned().unwrap_or(RValue::Null);
546    let units = named
547        .iter()
548        .find(|(n, _)| n == "units")
549        .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
550        .or_else(|| {
551            args.get(1)
552                .and_then(|v| v.as_vector()?.as_character_scalar())
553        })
554        .unwrap_or_else(|| "secs".to_string());
555    match val {
556        RValue::Vector(mut rv) => {
557            rv.set_attr(
558                "class".to_string(),
559                RValue::vec(Vector::Character(vec![Some("difftime".to_string())].into())),
560            );
561            rv.set_attr(
562                "units".to_string(),
563                RValue::vec(Vector::Character(vec![Some(units)].into())),
564            );
565            Ok(RValue::Vector(rv))
566        }
567        _ => Ok(val),
568    }
569}
570
571// endregion
572
573// region: Fast subset primitives
574
575/// .subset — fast subset without method dispatch.
576///
577/// @param x object to subset
578/// @param ... indices
579/// @return subset of x
580/// @namespace base
581#[builtin(name = ".subset", min_args = 1)]
582fn builtin_dot_subset(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
583    if args.len() < 2 {
584        return Ok(args.first().cloned().unwrap_or(RValue::Null));
585    }
586    // Delegate to regular [ indexing logic
587    let obj = &args[0];
588    let idx = &args[1];
589    match (obj, idx) {
590        (RValue::List(list), RValue::Vector(iv)) => {
591            if let Some(name) = iv.as_character_scalar() {
592                for (n, v) in &list.values {
593                    if n.as_deref() == Some(&name) {
594                        return Ok(v.clone());
595                    }
596                }
597                Ok(RValue::Null)
598            } else {
599                let i = iv.as_integer_scalar().unwrap_or(0) as usize;
600                if i > 0 && i <= list.values.len() {
601                    Ok(RValue::List(RList::new(vec![list.values[i - 1].clone()])))
602                } else {
603                    Ok(RValue::Null)
604                }
605            }
606        }
607        (RValue::Vector(v), RValue::Vector(iv)) => {
608            let i = iv.as_integer_scalar().unwrap_or(0) as usize;
609            if i > 0 && i <= v.len() {
610                Ok(crate::interpreter::indexing::extract_vector_element(
611                    v,
612                    i - 1,
613                ))
614            } else {
615                Ok(RValue::Null)
616            }
617        }
618        _ => Ok(RValue::Null),
619    }
620}
621
622/// .subset2 — fast [[ without method dispatch.
623///
624/// @param x object
625/// @param i index
626/// @return element
627/// @namespace base
628#[builtin(name = ".subset2", min_args = 2)]
629fn builtin_dot_subset2(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
630    // Same as [[ — extract single element
631    let obj = &args[0];
632    let idx = &args[1];
633    match (obj, idx) {
634        (RValue::List(list), RValue::Vector(iv)) => {
635            if let Some(name) = iv.as_character_scalar() {
636                for (n, v) in &list.values {
637                    if n.as_deref() == Some(&name) {
638                        return Ok(v.clone());
639                    }
640                }
641                Ok(RValue::Null)
642            } else {
643                let i = iv.as_integer_scalar().unwrap_or(0) as usize;
644                if i > 0 && i <= list.values.len() {
645                    Ok(list.values[i - 1].1.clone())
646                } else {
647                    Ok(RValue::Null)
648                }
649            }
650        }
651        (RValue::Vector(v), RValue::Vector(iv)) => {
652            if let Vector::Character(idx_names) = &iv.inner {
653                if let Some(Some(name)) = idx_names.first() {
654                    if let Some(names_attr) = v.get_attr("names") {
655                        if let Some(names_vec) = names_attr.as_vector() {
656                            let name_strs = names_vec.to_characters();
657                            for (j, n) in name_strs.iter().enumerate() {
658                                if n.as_deref() == Some(name.as_str()) && j < v.len() {
659                                    return Ok(
660                                        crate::interpreter::indexing::extract_vector_element(v, j),
661                                    );
662                                }
663                            }
664                        }
665                    }
666                    return Ok(RValue::Null);
667                }
668            }
669            let i = iv.as_integer_scalar().unwrap_or(0) as usize;
670            if i > 0 && i <= v.len() {
671                Ok(crate::interpreter::indexing::extract_vector_element(
672                    v,
673                    i - 1,
674                ))
675            } else {
676                Ok(RValue::Null)
677            }
678        }
679        _ => Ok(RValue::Null),
680    }
681}
682
683// endregion
684
685// region: S4 dispatch and misc stubs
686
687/// standardGeneric — S4 method dispatch.
688///
689/// @param f character: generic function name
690/// @return dispatched result
691/// @namespace methods
692#[builtin(name = "standardGeneric", namespace = "methods", min_args = 1)]
693fn builtin_standard_generic(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
694    Err(RError::new(
695        RErrorKind::Other,
696        "standardGeneric() dispatch not yet implemented — use S3 methods instead".to_string(),
697    ))
698}
699
700/// setIs — define class inheritance relationship.
701/// @namespace methods
702#[interpreter_builtin(name = "setIs", namespace = "methods")]
703fn interp_set_is(
704    _args: &[RValue],
705    _named: &[(String, RValue)],
706    context: &BuiltinContext,
707) -> Result<RValue, RError> {
708    context
709        .interpreter()
710        .write_stderr("[miniR stub] setIs() is a no-op in miniR\n");
711    Ok(RValue::Null)
712}
713
714/// removeClass — remove a class definition.
715/// @namespace methods
716#[interpreter_builtin(name = "removeClass", namespace = "methods")]
717fn interp_remove_class(
718    _args: &[RValue],
719    _named: &[(String, RValue)],
720    context: &BuiltinContext,
721) -> Result<RValue, RError> {
722    context
723        .interpreter()
724        .write_stderr("[miniR stub] removeClass() is a no-op in miniR\n");
725    Ok(RValue::Null)
726}
727
728/// resetGeneric — reset a generic function.
729/// @namespace methods
730#[interpreter_builtin(name = "resetGeneric", namespace = "methods")]
731fn interp_reset_generic(
732    _args: &[RValue],
733    _named: &[(String, RValue)],
734    context: &BuiltinContext,
735) -> Result<RValue, RError> {
736    context
737        .interpreter()
738        .write_stderr("[miniR stub] resetGeneric() is a no-op in miniR\n");
739    Ok(RValue::Null)
740}
741
742/// Encoding<- — set string encoding (no-op in UTF-8-only miniR).
743/// @namespace base
744#[builtin(name = "Encoding<-", min_args = 2)]
745fn builtin_encoding_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
746    // Return the string unchanged — miniR is always UTF-8
747    Ok(args.first().cloned().unwrap_or(RValue::Null))
748}
749
750/// bindtextdomain — bind a text domain for translations (no-op).
751/// @namespace base
752#[interpreter_builtin(name = "bindtextdomain")]
753fn interp_bindtextdomain(
754    _args: &[RValue],
755    _named: &[(String, RValue)],
756    context: &BuiltinContext,
757) -> Result<RValue, RError> {
758    context
759        .interpreter()
760        .write_stderr("[miniR stub] bindtextdomain() is a no-op in miniR — no i18n support\n");
761    Ok(RValue::Null)
762}
763
764/// eapply — apply function over environment bindings.
765/// @namespace base
766#[interpreter_builtin(name = "eapply", min_args = 2)]
767fn interp_eapply(
768    _args: &[RValue],
769    _named: &[(String, RValue)],
770    context: &BuiltinContext,
771) -> Result<RValue, RError> {
772    context
773        .interpreter()
774        .write_stderr("[miniR stub] eapply() is a stub in miniR — returns empty list\n");
775    Ok(RValue::List(RList::new(vec![])))
776}
777
778/// unlockBinding — unlock a locked binding.
779/// @namespace base
780#[builtin(name = "unlockBinding", min_args = 2)]
781fn builtin_unlock_binding(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
782    // Binding locks are advisory — silently succeed
783    Ok(RValue::Null)
784}
785
786/// sys.status — return system status (call stack info).
787/// @namespace base
788#[builtin(name = "sys.status")]
789fn builtin_sys_status(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
790    Ok(RValue::List(RList::new(vec![
791        (
792            Some("sys.calls".to_string()),
793            RValue::List(RList::new(vec![])),
794        ),
795        (
796            Some("sys.parents".to_string()),
797            RValue::vec(Vector::Integer(vec![].into())),
798        ),
799        (
800            Some("sys.frames".to_string()),
801            RValue::List(RList::new(vec![])),
802        ),
803    ])))
804}
805
806/// file.access — check file access permissions.
807/// @namespace base
808#[builtin(name = "file.access", min_args = 1)]
809fn builtin_file_access(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
810    let path = args
811        .first()
812        .and_then(|v| v.as_vector()?.as_character_scalar())
813        .unwrap_or_default();
814    let exists = std::path::Path::new(&path).exists();
815    Ok(RValue::vec(Vector::Integer(
816        vec![Some(if exists { 0 } else { -1 })].into(),
817    )))
818}
819
820/// serialize/unserialize — R object serialization (delegates to our RDS functions).
821/// @namespace base
822#[builtin(name = "serialize", min_args = 2)]
823fn builtin_serialize(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
824    Err(RError::new(
825        RErrorKind::Other,
826        "serialize() to connection not yet implemented — use saveRDS() instead".to_string(),
827    ))
828}
829
830/// @namespace base
831#[builtin(name = "unserialize", min_args = 1)]
832fn builtin_unserialize(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
833    Err(RError::new(
834        RErrorKind::Other,
835        "unserialize() from connection not yet implemented — use readRDS() instead".to_string(),
836    ))
837}
838
839/// tracemem/untracemem/retracemem — memory tracing (no-ops).
840/// @namespace base
841#[builtin(name = "tracemem", min_args = 1)]
842fn builtin_tracemem(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
843    Ok(RValue::vec(Vector::Character(
844        vec![Some(format!("<{:p}>", &args[0]))].into(),
845    )))
846}
847#[builtin(name = "untracemem", min_args = 1)]
848fn builtin_untracemem(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
849    Ok(RValue::Null)
850}
851#[builtin(name = "retracemem")]
852fn builtin_retracemem(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
853    Ok(RValue::Null)
854}
855
856// endregion
857
858// region: Connection stubs
859
860stub_builtin!("rawConnection", 1, "rawConnection() not yet implemented");
861stub_builtin!("textConnection", 1, "textConnection() not yet implemented");
862stub_builtin!("pipe", 1, "pipe() not yet implemented");
863stub_builtin!("fifo", 1, "fifo() not yet implemented");
864stub_builtin!(
865    "socketConnection",
866    1,
867    "socketConnection() not yet implemented — use make.socket() instead"
868);
869stub_builtin!("gzcon", 1, "gzcon() not yet implemented");
870stub_builtin!("readBin", 1, "readBin() not yet implemented");
871stub_builtin!("writeBin", 1, "writeBin() not yet implemented");
872stub_builtin!("readChar", 1, "readChar() not yet implemented");
873stub_builtin!("writeChar", 1, "writeChar() not yet implemented");
874stub_builtin!("memCompress", 1, "memCompress() not yet implemented");
875stub_builtin!("memDecompress", 1, "memDecompress() not yet implemented");
876
877// endregion
878
879// region: Interactive/session utilities
880
881/// interactive — check if R is running interactively.
882///
883/// @return logical scalar
884/// @namespace base
885#[builtin(name = "interactive")]
886fn builtin_interactive(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
887    // Return TRUE for REPL, FALSE for scripts — for now always FALSE
888    // since we can't distinguish from a plain builtin
889    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
890}
891
892/// eval.parent — evaluate expression in the calling environment.
893///
894/// @param expr expression to evaluate
895/// @param n number of frames to go up (default 1)
896/// @return result of evaluation
897/// @namespace base
898#[builtin(name = "eval.parent", min_args = 1)]
899fn builtin_eval_parent(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
900    // Simplified — just return the first arg (can't access call stack from plain builtin)
901    Ok(args.first().cloned().unwrap_or(RValue::Null))
902}
903
904/// length<- — set the length of a vector.
905///
906/// @param x vector
907/// @param value new length
908/// @return vector with adjusted length (truncated or extended with NA)
909/// @namespace base
910#[builtin(name = "length<-", min_args = 2)]
911fn builtin_length_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
912    let new_len = args
913        .get(1)
914        .and_then(|v| v.as_vector()?.as_integer_scalar())
915        .unwrap_or(0) as usize;
916
917    match args.first() {
918        Some(RValue::Vector(v)) => {
919            let mut doubles = v.to_doubles();
920            doubles.resize(new_len, None);
921            Ok(RValue::vec(Vector::Double(doubles.into())))
922        }
923        Some(RValue::List(list)) => {
924            let mut values = list.values.clone();
925            values.resize(new_len, (None, RValue::Null));
926            Ok(RValue::List(RList::new(values)))
927        }
928        _ => Ok(RValue::Null),
929    }
930}
931
932/// levels<- — set factor levels.
933///
934/// @param x factor
935/// @param value new levels
936/// @return factor with updated levels
937/// @namespace base
938#[builtin(name = "levels<-", min_args = 2)]
939fn builtin_levels_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
940    match args.first() {
941        Some(RValue::Vector(v)) => {
942            let mut rv = v.clone();
943            let new_levels = args.get(1).cloned().unwrap_or(RValue::Null);
944            rv.set_attr("levels".to_string(), new_levels);
945            Ok(RValue::Vector(rv))
946        }
947        _ => Ok(args.first().cloned().unwrap_or(RValue::Null)),
948    }
949}
950
951/// file.append — append contents of one file to another.
952///
953/// @param file1 destination file
954/// @param file2 source file
955/// @return logical scalar
956/// @namespace base
957#[builtin(name = "file.append", min_args = 2)]
958fn builtin_file_append(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
959    let file1 = args
960        .first()
961        .and_then(|v| v.as_vector()?.as_character_scalar())
962        .unwrap_or_default();
963    let file2 = args
964        .get(1)
965        .and_then(|v| v.as_vector()?.as_character_scalar())
966        .unwrap_or_default();
967    let result = (|| {
968        let content = std::fs::read(&file2).ok()?;
969        use std::io::Write;
970        let mut f = std::fs::OpenOptions::new().append(true).open(&file1).ok()?;
971        f.write_all(&content).ok()
972    })();
973    Ok(RValue::vec(Vector::Logical(
974        vec![Some(result.is_some())].into(),
975    )))
976}
977
978/// Cstack_info — C stack info (returns dummy values).
979/// @namespace base
980#[builtin(name = "Cstack_info")]
981fn builtin_cstack_info(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
982    let mut rv = RVector::from(Vector::Integer(
983        vec![Some(8388608), Some(16384), Some(0)].into(),
984    ));
985    rv.set_attr(
986        "names".to_string(),
987        RValue::vec(Vector::Character(
988            vec![
989                Some("size".to_string()),
990                Some("current".to_string()),
991                Some("direction".to_string()),
992            ]
993            .into(),
994        )),
995    );
996    Ok(RValue::Vector(rv))
997}
998
999/// extSoftVersion — external software version info.
1000/// @namespace base
1001#[builtin(name = "extSoftVersion")]
1002fn builtin_ext_soft_version(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1003    let mut rv = RVector::from(Vector::Character(
1004        vec![
1005            Some("".to_string()),
1006            Some("1.1.1".to_string()),
1007            Some("".to_string()),
1008            Some("".to_string()),
1009        ]
1010        .into(),
1011    ));
1012    rv.set_attr(
1013        "names".to_string(),
1014        RValue::vec(Vector::Character(
1015            vec![
1016                Some("zlib".to_string()),
1017                Some("bzlib".to_string()),
1018                Some("xz".to_string()),
1019                Some("PCRE".to_string()),
1020            ]
1021            .into(),
1022        )),
1023    );
1024    Ok(RValue::Vector(rv))
1025}
1026
1027// endregion
1028
1029// region: Dynamic loading stubs (only when native feature is off)
1030
1031#[cfg(not(feature = "native"))]
1032/// dyn.load — stub for dynamic loading of shared libraries.
1033/// @namespace base
1034#[builtin(name = "dyn.load", min_args = 1)]
1035fn builtin_dyn_load(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1036    let path = args
1037        .first()
1038        .and_then(|v| v.as_vector()?.as_character_scalar())
1039        .unwrap_or_default();
1040    debug!(path = path.as_str(), "dyn.load");
1041    Err(RError::other(
1042        "dyn.load() not available — miniR cannot load compiled shared libraries",
1043    ))
1044}
1045
1046#[cfg(not(feature = "native"))]
1047/// dyn.unload — stub for unloading shared libraries.
1048/// @namespace base
1049#[builtin(name = "dyn.unload", min_args = 1)]
1050fn builtin_dyn_unload(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1051    let name = args
1052        .first()
1053        .and_then(|v| v.as_vector()?.as_character_scalar())
1054        .unwrap_or_default();
1055    debug!(name = name.as_str(), "dyn.unload");
1056    Err(RError::other("dyn.unload() not available"))
1057}
1058#[cfg(not(feature = "native"))]
1059stub_builtin!(
1060    "library.dynam",
1061    1,
1062    "library.dynam() not available — miniR cannot load compiled code"
1063);
1064#[cfg(not(feature = "native"))]
1065stub_builtin!(
1066    "library.dynam.unload",
1067    1,
1068    "library.dynam.unload() not available"
1069);
1070
1071#[cfg(not(feature = "native"))]
1072/// is.loaded — check if a C symbol is loaded (always FALSE).
1073/// @namespace base
1074#[builtin(name = "is.loaded", min_args = 1)]
1075fn builtin_is_loaded(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1076    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
1077}
1078
1079#[cfg(not(feature = "native"))]
1080/// getNativeSymbolInfo — get info about a loaded symbol (always errors).
1081/// @namespace base
1082#[builtin(name = "getNativeSymbolInfo", min_args = 1)]
1083fn builtin_get_native_symbol_info(
1084    args: &[RValue],
1085    _: &[(String, RValue)],
1086) -> Result<RValue, RError> {
1087    let name = args
1088        .first()
1089        .and_then(|v| v.as_vector()?.as_character_scalar())
1090        .unwrap_or_default();
1091    Err(RError::new(
1092        RErrorKind::Other,
1093        format!("no such symbol '{name}' — miniR cannot load native code"),
1094    ))
1095}
1096
1097// endregion
1098
1099// region: Debugging stubs
1100
1101/// debugonce — set a one-time debug flag (no-op).
1102/// @namespace base
1103#[interpreter_builtin(name = "debugonce")]
1104fn interp_debugonce(
1105    _args: &[RValue],
1106    _named: &[(String, RValue)],
1107    context: &BuiltinContext,
1108) -> Result<RValue, RError> {
1109    context
1110        .interpreter()
1111        .write_stderr("[miniR stub] debugonce() is a no-op in miniR — no debugger\n");
1112    Ok(RValue::Null)
1113}
1114
1115/// trace — set tracing on a function (no-op stub).
1116/// @namespace base
1117#[interpreter_builtin(name = "trace")]
1118fn interp_trace(
1119    _args: &[RValue],
1120    _named: &[(String, RValue)],
1121    context: &BuiltinContext,
1122) -> Result<RValue, RError> {
1123    context
1124        .interpreter()
1125        .write_stderr("[miniR stub] trace() is a no-op in miniR — no debugger\n");
1126    Ok(RValue::Null)
1127}
1128
1129/// untrace — remove tracing (no-op stub).
1130/// @namespace base
1131#[interpreter_builtin(name = "untrace")]
1132fn interp_untrace(
1133    _args: &[RValue],
1134    _named: &[(String, RValue)],
1135    context: &BuiltinContext,
1136) -> Result<RValue, RError> {
1137    context
1138        .interpreter()
1139        .write_stderr("[miniR stub] untrace() is a no-op in miniR — no debugger\n");
1140    Ok(RValue::Null)
1141}
1142
1143/// browseEnv — open environment browser (no-op stub).
1144/// @namespace utils
1145#[interpreter_builtin(name = "browseEnv", namespace = "utils")]
1146fn interp_browse_env(
1147    _args: &[RValue],
1148    _named: &[(String, RValue)],
1149    context: &BuiltinContext,
1150) -> Result<RValue, RError> {
1151    context
1152        .interpreter()
1153        .write_stderr("[miniR stub] browseEnv() is a no-op in miniR\n");
1154    Ok(RValue::Null)
1155}
1156
1157// endregion
1158
1159// region: Connection management stubs
1160
1161stub_builtin!("sink", 0, "sink() not yet implemented");
1162stub_builtin!("flush", 1, "flush() not yet implemented for connections");
1163stub_builtin!(
1164    "showConnections",
1165    0,
1166    "showConnections() not yet implemented"
1167);
1168stub_builtin!(
1169    "getAllConnections",
1170    0,
1171    "getAllConnections() not yet implemented"
1172);
1173stub_builtin!("pushBack", 1, "pushBack() not yet implemented");
1174stub_builtin!("pushBackLength", 1);
1175stub_builtin!("clearPushBack", 1);
1176stub_builtin!("seek", 1, "seek() not yet implemented");
1177stub_builtin!("truncate", 1, "truncate() not yet implemented");
1178stub_builtin!("isSeekable", 1);
1179stub_builtin!("isIncomplete", 1);
1180stub_builtin!("summary.connection", 1);
1181
1182// endregion
1183
1184// region: Filesystem stubs
1185
1186/// Sys.readlink — read a symbolic link target.
1187/// @namespace base
1188#[builtin(name = "Sys.readlink", min_args = 1)]
1189fn builtin_sys_readlink(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1190    let path = args
1191        .first()
1192        .and_then(|v| v.as_vector()?.as_character_scalar())
1193        .unwrap_or_default();
1194    let target = std::fs::read_link(&path)
1195        .ok()
1196        .map(|p| p.to_string_lossy().to_string())
1197        .unwrap_or_default();
1198    Ok(RValue::vec(Vector::Character(vec![Some(target)].into())))
1199}
1200
1201/// file.link — create a hard link.
1202/// @namespace base
1203#[builtin(name = "file.link", min_args = 2)]
1204fn builtin_file_link(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1205    let from = args
1206        .first()
1207        .and_then(|v| v.as_vector()?.as_character_scalar())
1208        .unwrap_or_default();
1209    let to = args
1210        .get(1)
1211        .and_then(|v| v.as_vector()?.as_character_scalar())
1212        .unwrap_or_default();
1213    let ok = std::fs::hard_link(&from, &to).is_ok();
1214    Ok(RValue::vec(Vector::Logical(vec![Some(ok)].into())))
1215}
1216
1217/// file.symlink — create a symbolic link.
1218/// @namespace base
1219#[builtin(name = "file.symlink", min_args = 2)]
1220fn builtin_file_symlink(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1221    let from = args
1222        .first()
1223        .and_then(|v| v.as_vector()?.as_character_scalar())
1224        .unwrap_or_default();
1225    let to = args
1226        .get(1)
1227        .and_then(|v| v.as_vector()?.as_character_scalar())
1228        .unwrap_or_default();
1229    #[cfg(unix)]
1230    let ok = std::os::unix::fs::symlink(&from, &to).is_ok();
1231    #[cfg(not(unix))]
1232    let ok = false;
1233    Ok(RValue::vec(Vector::Logical(vec![Some(ok)].into())))
1234}
1235
1236/// Sys.chmod — change file permissions (Unix only).
1237/// @namespace base
1238#[interpreter_builtin(name = "Sys.chmod", min_args = 1)]
1239fn interp_sys_chmod(
1240    _args: &[RValue],
1241    _named: &[(String, RValue)],
1242    context: &BuiltinContext,
1243) -> Result<RValue, RError> {
1244    context
1245        .interpreter()
1246        .write_stderr("[miniR stub] Sys.chmod() is a no-op in miniR\n");
1247    Ok(RValue::Null)
1248}
1249
1250/// Sys.umask — get/set file creation mask (stub).
1251/// @namespace base
1252#[builtin(name = "Sys.umask")]
1253fn builtin_sys_umask(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1254    Ok(RValue::vec(Vector::Integer(vec![Some(0o022)].into())))
1255}
1256
1257/// Sys.setFileTime — set file modification time.
1258/// @namespace base
1259#[builtin(name = "Sys.setFileTime", min_args = 2)]
1260fn builtin_sys_set_file_time(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1261    // Stub — setting file times requires platform-specific APIs
1262    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
1263}
1264
1265// endregion
1266
1267// region: Task callbacks (no-op stubs)
1268
1269/// getTaskCallbackNames — list registered task callbacks.
1270/// @namespace base
1271#[builtin(name = "getTaskCallbackNames")]
1272fn builtin_get_task_callback_names(
1273    _args: &[RValue],
1274    _: &[(String, RValue)],
1275) -> Result<RValue, RError> {
1276    Ok(RValue::vec(Vector::Character(vec![].into())))
1277}
1278
1279/// addTaskCallback — register a task callback (no-op).
1280/// @namespace base
1281#[builtin(name = "addTaskCallback", min_args = 1)]
1282fn builtin_add_task_callback(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1283    Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
1284}
1285
1286/// removeTaskCallback — remove a task callback (no-op).
1287/// @namespace base
1288#[builtin(name = "removeTaskCallback", min_args = 1)]
1289fn builtin_remove_task_callback(
1290    _args: &[RValue],
1291    _: &[(String, RValue)],
1292) -> Result<RValue, RError> {
1293    Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
1294}
1295
1296// endregion
1297
1298// region: Misc stubs
1299
1300/// gc.time — get garbage collection timing (always zero).
1301/// @namespace base
1302#[builtin(name = "gc.time")]
1303fn builtin_gc_time(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1304    Ok(RValue::vec(Vector::Double(
1305        vec![Some(0.0), Some(0.0), Some(0.0)].into(),
1306    )))
1307}
1308
1309/// mem.limits — get memory limits (dummy values).
1310/// @namespace base
1311#[builtin(name = "mem.limits")]
1312fn builtin_mem_limits(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1313    Ok(RValue::vec(Vector::Double(
1314        vec![Some(f64::INFINITY), Some(f64::INFINITY)].into(),
1315    )))
1316}
1317
1318/// memory.profile — profile memory usage (dummy).
1319/// @namespace base
1320#[builtin(name = "memory.profile")]
1321fn builtin_memory_profile(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1322    Ok(RValue::vec(Vector::Integer(vec![].into())))
1323}
1324
1325/// pos.to.env — convert search path position to environment.
1326/// @namespace base
1327#[builtin(name = "pos.to.env", min_args = 1)]
1328fn builtin_pos_to_env(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1329    let _pos = args
1330        .first()
1331        .and_then(|v| v.as_vector()?.as_integer_scalar())
1332        .unwrap_or(1);
1333    // Can't return proper environment from plain builtin — return NULL
1334    Ok(RValue::Null)
1335}
1336
1337/// setNames — set names on an object and return it.
1338///
1339/// @param object any R object
1340/// @param nm character vector of names
1341/// @return the object with names set
1342/// @namespace stats
1343// CRAN: used by 100+ packages (stats::setNames, base::setNames)
1344#[builtin(name = "setNames", namespace = "stats", min_args = 2)]
1345fn builtin_set_names(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1346    let obj = args.first().cloned().unwrap_or(RValue::Null);
1347    let names = args.get(1).cloned().unwrap_or(RValue::Null);
1348    match obj {
1349        RValue::Vector(mut rv) => {
1350            rv.set_attr("names".to_string(), names);
1351            Ok(RValue::Vector(rv))
1352        }
1353        RValue::List(mut list) => {
1354            if let Some(names_vec) = names.as_vector() {
1355                let name_strs = names_vec.to_characters();
1356                for (i, (n, _)) in list.values.iter_mut().enumerate() {
1357                    if let Some(new_name) = name_strs.get(i) {
1358                        *n = new_name.clone();
1359                    }
1360                }
1361            }
1362            Ok(RValue::List(list))
1363        }
1364        _ => Ok(obj),
1365    }
1366}
1367
1368/// globalVariables — declare global variables (no-op, suppresses R CMD check notes).
1369///
1370/// @param names character vector of variable names
1371/// @param package package name (ignored)
1372/// @param add logical (ignored)
1373/// @return invisible NULL
1374/// @namespace utils
1375// CRAN: used by many packages to suppress "no visible binding" notes
1376#[builtin(name = "globalVariables", namespace = "utils")]
1377fn builtin_global_variables(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1378    Ok(RValue::Null)
1379}
1380
1381/// withAutoprint — evaluate expressions with auto-printing (stub).
1382/// @namespace base
1383#[builtin(name = "withAutoprint")]
1384fn builtin_with_autoprint(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1385    Ok(args.first().cloned().unwrap_or(RValue::Null))
1386}
1387
1388/// signature — create an S4 method signature.
1389///
1390/// @param ... named arguments specifying class for each formal
1391/// @return named character vector
1392/// @namespace methods
1393// GNU-R-methods: used by S4 setMethod calls
1394#[builtin(name = "signature", namespace = "methods")]
1395fn builtin_signature(_args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1396    let names: Vec<Option<String>> = named.iter().map(|(n, _)| Some(n.clone())).collect();
1397    let values: Vec<Option<String>> = named
1398        .iter()
1399        .map(|(_, v)| v.as_vector().and_then(|vec| vec.as_character_scalar()))
1400        .collect();
1401    let mut rv = RVector::from(Vector::Character(values.into()));
1402    rv.set_attr(
1403        "names".to_string(),
1404        RValue::vec(Vector::Character(names.into())),
1405    );
1406    Ok(RValue::Vector(rv))
1407}
1408
1409/// prototype — create an S4 class prototype (returns a list).
1410///
1411/// @param ... named default values for slots
1412/// @return named list
1413/// @namespace methods
1414// GNU-R-methods: used in setClass() calls
1415#[builtin(name = "prototype", namespace = "methods")]
1416fn builtin_prototype(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1417    let mut values: Vec<(Option<String>, RValue)> = named
1418        .iter()
1419        .map(|(n, v)| (Some(n.clone()), v.clone()))
1420        .collect();
1421    // Also include positional args
1422    for arg in args {
1423        values.push((None, arg.clone()));
1424    }
1425    Ok(RValue::List(RList::new(values)))
1426}
1427
1428/// lengths — get lengths of list elements.
1429///
1430/// @param x list or vector
1431/// @return integer vector of lengths
1432/// @namespace base
1433// CRAN: used by many packages (base::lengths)
1434#[builtin(min_args = 1)]
1435fn builtin_lengths(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1436    match args.first() {
1437        Some(RValue::List(list)) => {
1438            let lens: Vec<Option<i64>> = list
1439                .values
1440                .iter()
1441                .map(|(_, v)| {
1442                    Some(match v {
1443                        RValue::Vector(rv) => rv.len() as i64,
1444                        RValue::List(l) => l.values.len() as i64,
1445                        RValue::Null => 0,
1446                        _ => 1,
1447                    })
1448                })
1449                .collect();
1450            Ok(RValue::vec(Vector::Integer(lens.into())))
1451        }
1452        Some(RValue::Vector(v)) => {
1453            // For atomic vectors, each element has length 1
1454            let lens: Vec<Option<i64>> = (0..v.len()).map(|_| Some(1)).collect();
1455            Ok(RValue::vec(Vector::Integer(lens.into())))
1456        }
1457        _ => Ok(RValue::vec(Vector::Integer(vec![].into()))),
1458    }
1459}
1460
1461/// commandArgs — return command-line arguments.
1462///
1463/// @param trailingOnly if TRUE, return only args after --args
1464/// @return character vector
1465/// @namespace base
1466#[builtin(name = "commandArgs")]
1467fn builtin_command_args(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1468    let trailing_only = args
1469        .first()
1470        .and_then(|v| v.as_vector()?.as_logical_scalar())
1471        .unwrap_or(false);
1472    let cli_args: Vec<String> = std::env::args().collect();
1473    let result = if trailing_only {
1474        // Return args after "--args"
1475        if let Some(pos) = cli_args.iter().position(|a| a == "--args") {
1476            cli_args[pos + 1..].to_vec()
1477        } else {
1478            vec![]
1479        }
1480    } else {
1481        cli_args
1482    };
1483    Ok(RValue::vec(Vector::Character(
1484        result.into_iter().map(Some).collect::<Vec<_>>().into(),
1485    )))
1486}
1487
1488// endregion
1489
1490// region: TLS stub (when tls feature is disabled)
1491
1492#[cfg(not(feature = "tls"))]
1493stub_builtin!(
1494    "url",
1495    1,
1496    "url() requires the 'tls' feature — rebuild miniR with --features tls"
1497);
1498
1499// endregion
1500
1501stub_builtin!("arity", 1);
1502/// is.object — check if an object has a class attribute.
1503/// @param x any R value
1504/// @return logical
1505/// @namespace base
1506#[builtin(name = "is.object", min_args = 1)]
1507fn builtin_is_object(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1508    let has_class = match args.first() {
1509        Some(RValue::Vector(rv)) => rv.get_attr("class").is_some(),
1510        Some(RValue::List(l)) => l.get_attr("class").is_some(),
1511        _ => false,
1512    };
1513    Ok(RValue::vec(Vector::Logical(vec![Some(has_class)].into())))
1514}
1515
1516/// isS4 — check if an object is an S4 object.
1517/// @param object any R value
1518/// @return logical
1519/// @namespace base
1520#[builtin(name = "isS4", min_args = 1)]
1521fn builtin_is_s4(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1522    // miniR doesn't have real S4 objects, so always FALSE
1523    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
1524}
1525
1526/// registerS3method — register an S3 method in a namespace.
1527/// @param genname character: generic function name
1528/// @param class character: class name
1529/// @param method function: the method
1530/// @param envir environment: where to register
1531/// @namespace base
1532#[interpreter_builtin(name = "registerS3method", min_args = 3)]
1533fn builtin_register_s3_method(
1534    args: &[RValue],
1535    _named: &[(String, RValue)],
1536    ctx: &BuiltinContext,
1537) -> Result<RValue, RError> {
1538    let generic = args
1539        .first()
1540        .and_then(|v| v.as_vector()?.as_character_scalar())
1541        .unwrap_or_default();
1542    let class = args
1543        .get(1)
1544        .and_then(|v| v.as_vector()?.as_character_scalar())
1545        .unwrap_or_default();
1546    let method = args.get(2).cloned().unwrap_or(RValue::Null);
1547
1548    if !generic.is_empty() && !class.is_empty() {
1549        ctx.interpreter()
1550            .s3_method_registry
1551            .borrow_mut()
1552            .insert((generic, class), method);
1553    }
1554    Ok(RValue::Null)
1555}
1556
1557/// isatty — check if a connection is a terminal.
1558/// @param con integer: connection number (0=stdin, 1=stdout, 2=stderr)
1559/// @return logical
1560/// @namespace base
1561#[builtin(name = "isatty", min_args = 1)]
1562fn builtin_isatty(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1563    let con = args
1564        .first()
1565        .and_then(|v| v.as_vector()?.as_integer_scalar())
1566        .unwrap_or(0);
1567    // stdin/stdout/stderr — check if interactive
1568    let result = match con {
1569        0 => std::io::IsTerminal::is_terminal(&std::io::stdin()),
1570        1 => std::io::IsTerminal::is_terminal(&std::io::stdout()),
1571        2 => std::io::IsTerminal::is_terminal(&std::io::stderr()),
1572        _ => false,
1573    };
1574    Ok(RValue::vec(Vector::Logical(vec![Some(result)].into())))
1575}
1576
1577/// conflictRules — returns NULL (no conflict management in miniR).
1578/// @namespace base
1579#[builtin(name = "conflictRules", min_args = 1)]
1580fn builtin_conflict_rules(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1581    Ok(RValue::Null)
1582}
1583
1584/// setHook — no-op in miniR.
1585/// @namespace base
1586#[builtin(name = "setHook")]
1587fn builtin_set_hook(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1588    Ok(RValue::Null)
1589}
1590
1591/// packageEvent — create a package event name string.
1592/// @namespace base
1593#[builtin(name = "packageEvent", min_args = 1)]
1594fn builtin_package_event(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1595    let pkg = args
1596        .first()
1597        .and_then(|v| v.as_vector()?.as_character_scalar())
1598        .unwrap_or_default();
1599    let event = args
1600        .get(1)
1601        .and_then(|v| v.as_vector()?.as_character_scalar())
1602        .unwrap_or_else(|| "onLoad".to_string());
1603    Ok(RValue::vec(Vector::Character(
1604        vec![Some(format!("{event}:{pkg}"))].into(),
1605    )))
1606}
1607
1608/// getHook — returns NULL (no hooks in miniR).
1609/// @namespace base
1610#[builtin(name = "getHook", min_args = 1)]
1611fn builtin_get_hook(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1612    Ok(RValue::Null)
1613}
1614
1615// endregion
1616
1617// region: Linear algebra stubs
1618
1619/// Singular Value Decomposition.
1620///
1621/// MASS needs this for `ginv()`. Not yet implemented — returns an error
1622/// directing the user to the missing functionality.
1623///
1624/// @param x a numeric matrix
1625/// @param nu number of left singular vectors (ignored)
1626/// @param nv number of right singular vectors (ignored)
1627/// @return list with components d, u, v
1628/// @namespace base
1629#[cfg(not(feature = "linalg"))]
1630#[builtin(name = "svd", min_args = 1)]
1631fn builtin_svd(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1632    Err(RError::other(
1633        "svd() is not yet implemented in miniR. \
1634         Singular value decomposition requires a linear algebra backend (e.g. nalgebra). \
1635         This will be available when the 'linalg' feature is implemented.",
1636    ))
1637}
1638
1639// endregion
1640
1641// region: Compatibility shims
1642
1643/// Convert to R boolean — used by grDevices and other internal code.
1644///
1645/// This is equivalent to `as.logical(x)[[1]]` — coerces to a scalar logical.
1646///
1647/// @param x object to coerce
1648/// @return logical scalar
1649/// @namespace base
1650#[builtin(name = "asRboolean", min_args = 1)]
1651fn builtin_as_r_boolean(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1652    match args.first() {
1653        Some(RValue::Vector(v)) => {
1654            let logicals = v.to_logicals();
1655            Ok(RValue::vec(Vector::Logical(
1656                vec![logicals.first().copied().flatten()].into(),
1657            )))
1658        }
1659        Some(RValue::Null) => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
1660        _ => Ok(RValue::vec(Vector::Logical(vec![None].into()))),
1661    }
1662}