Skip to main content

r/interpreter/
builtins.rs

1mod args;
2mod coercion;
3#[cfg(feature = "collections")]
4pub mod collections;
5mod conditions;
6pub mod connections;
7mod dataframes;
8#[cfg(feature = "datetime")]
9mod datetime;
10#[cfg(any(feature = "digest", feature = "blake3"))]
11mod digest;
12mod factors;
13pub mod graphics;
14pub mod grid;
15mod interp;
16#[cfg(feature = "io")]
17pub mod io;
18#[cfg(feature = "json")]
19mod json;
20pub mod math;
21#[cfg(feature = "native")]
22mod native_code;
23#[cfg(feature = "tls")]
24mod net;
25#[cfg(feature = "parquet")]
26mod parquet;
27mod pre_eval;
28#[cfg(feature = "progress")]
29pub mod progress;
30#[cfg(feature = "random")]
31mod random;
32#[cfg(feature = "native")]
33pub mod rlang_ffi;
34mod s4;
35pub mod serialize;
36mod stats;
37pub mod strings;
38mod stubs;
39pub mod system;
40mod tables;
41#[cfg(feature = "tables")]
42mod tables_display;
43#[cfg(feature = "toml")]
44mod toml;
45mod types;
46
47use unicode_width::UnicodeWidthStr;
48
49use crate::interpreter::environment::Environment;
50use crate::interpreter::value::*;
51use crate::interpreter::BuiltinContext;
52use crate::parser::ast::{Arg, Param};
53use itertools::Itertools;
54use linkme::distributed_slice;
55use minir_macros::{builtin, interpreter_builtin};
56
57pub use crate::interpreter::value::{
58    BuiltinDescriptor, BuiltinFn, BuiltinImplementation, InterpreterBuiltinFn, PreEvalBuiltinFn,
59};
60pub(crate) use args::CallArgs;
61
62#[distributed_slice]
63pub static BUILTIN_REGISTRY: [BuiltinDescriptor];
64
65fn register_builtin_binding(env: &Environment, binding_name: &str, descriptor: BuiltinDescriptor) {
66    env.set(
67        binding_name.to_string(),
68        RValue::Function(RFunction::Builtin {
69            name: binding_name.to_string(),
70            implementation: descriptor.implementation,
71            min_args: descriptor.min_args,
72            max_args: descriptor.max_args,
73            formals: descriptor.formals,
74        }),
75    );
76}
77
78/// Helper for unary math builtins: applies `f64 -> f64` element-wise.
79#[inline]
80pub fn math_unary_op(args: &[RValue], f: fn(f64) -> f64) -> Result<RValue, RError> {
81    match args.first() {
82        Some(RValue::Vector(v)) => {
83            let result: Vec<Option<f64>> = v.to_doubles().iter().map(|x| x.map(f)).collect();
84            Ok(RValue::vec(Vector::Double(result.into())))
85        }
86        _ => Err(RError::new(
87            RErrorKind::Argument,
88            "non-numeric argument to mathematical function".to_string(),
89        )),
90    }
91}
92
93/// Look up a builtin descriptor by R name or alias.
94pub fn find_builtin(name: &str) -> Option<&'static BuiltinDescriptor> {
95    BUILTIN_REGISTRY
96        .iter()
97        .find(|d| d.name == name || d.aliases.contains(&name))
98}
99
100/// Look up a builtin by namespace::name (e.g. "stats::cor").
101pub fn find_builtin_ns(namespace: &str, name: &str) -> Option<&'static BuiltinDescriptor> {
102    BUILTIN_REGISTRY
103        .iter()
104        .find(|d| d.namespace == namespace && (d.name == name || d.aliases.contains(&name)))
105}
106
107/// Format a builtin's doc string for display.
108/// Convention: first line = title, rest = description/params.
109pub fn format_help(descriptor: &BuiltinDescriptor) -> String {
110    let mut out = String::new();
111    let header = format!("{}::{}", descriptor.namespace, descriptor.name);
112    out.push_str(&format!("{header}\n"));
113    out.push_str(&"─".repeat(header.len().max(20)));
114    out.push('\n');
115
116    if descriptor.doc.is_empty() {
117        out.push_str(&format!(
118            "  .Primitive(\"{}\")  [{} arg{}]\n",
119            descriptor.name,
120            descriptor.min_args,
121            if descriptor.min_args == 1 { "" } else { "s" }
122        ));
123    } else {
124        for line in descriptor.doc.lines() {
125            let line = line.trim();
126            if let Some(rest) = line.strip_prefix("@param ") {
127                if let Some((param, desc)) = rest.split_once(' ') {
128                    out.push_str(&format!("  {:<12} {}\n", param, desc));
129                } else {
130                    out.push_str(&format!("  {}\n", rest));
131                }
132            } else if let Some(ret) = line.strip_prefix("@return ") {
133                out.push_str(&format!("\nReturns: {ret}\n"));
134            } else if !line.is_empty() {
135                out.push_str(&format!("{}\n", line));
136            }
137        }
138    }
139
140    if !descriptor.aliases.is_empty() {
141        out.push_str(&format!("\nAliases: {}\n", descriptor.aliases.join(", ")));
142    }
143    out
144}
145
146/// Extract parameter names from a doc string's `@param name ...` lines.
147fn extract_param_names_from_doc(doc: &str) -> Vec<String> {
148    doc.lines()
149        .filter_map(|line| {
150            let line = line.trim();
151            line.strip_prefix("@param ")
152                .and_then(|rest| rest.split_whitespace().next().map(|name| name.to_string()))
153        })
154        .collect()
155}
156
157/// Synthesize `RdDoc` help pages from all builtin descriptors and register
158/// them in the help index. This gives every builtin a rich help page
159/// (title, usage, arguments, return value) derived from rustdoc comments.
160pub fn synthesize_builtin_help(index: &mut crate::interpreter::packages::rd::RdHelpIndex) {
161    use crate::interpreter::packages::rd::{RdDoc, RdIndexEntry};
162
163    for desc in BUILTIN_REGISTRY {
164        if desc.doc.is_empty() {
165            continue;
166        }
167
168        let mut title = None;
169        let mut description_lines = Vec::new();
170        let mut arguments = Vec::new();
171        let mut return_val = None;
172        let mut in_description = false;
173
174        for line in desc.doc.lines() {
175            let line = line.trim();
176            if let Some(rest) = line.strip_prefix("@param ") {
177                in_description = false;
178                if let Some((param, param_desc)) = rest.split_once(' ') {
179                    arguments.push((param.to_string(), param_desc.to_string()));
180                }
181            } else if let Some(ret) = line.strip_prefix("@return ") {
182                in_description = false;
183                return_val = Some(ret.to_string());
184            } else if line.starts_with('@') {
185                // skip other tags (@namespace, etc.)
186                in_description = false;
187            } else if title.is_none() && !line.is_empty() {
188                title = Some(line.to_string());
189                in_description = true;
190            } else if in_description && !line.is_empty() {
191                description_lines.push(line.to_string());
192            } else if line.is_empty() && in_description && !description_lines.is_empty() {
193                // blank line in description — keep going
194                description_lines.push(String::new());
195            }
196        }
197
198        // Build usage string
199        let params = extract_param_names_from_doc(desc.doc);
200        let usage = if params.is_empty() {
201            format!("{}(...)", desc.name)
202        } else {
203            format!("{}({})", desc.name, params.join(", "))
204        };
205
206        let description = if description_lines.is_empty() {
207            None
208        } else {
209            Some(description_lines.join("\n").trim_end().to_string())
210        };
211
212        let doc = RdDoc {
213            name: Some(desc.name.to_string()),
214            aliases: desc
215                .aliases
216                .iter()
217                .map(|a| a.to_string())
218                .chain(std::iter::once(desc.name.to_string()))
219                .collect(),
220            title,
221            description,
222            usage: Some(usage),
223            arguments,
224            value: return_val,
225            ..Default::default()
226        };
227
228        let ns = if desc.namespace.is_empty() {
229            "base"
230        } else {
231            desc.namespace
232        };
233
234        let entry = RdIndexEntry {
235            package: ns.to_string(),
236            file_path: format!("<builtin:{}>", desc.name),
237            doc,
238        };
239
240        // Register under name and all aliases
241        index.register_entry(desc.name, entry.clone());
242        for alias in desc.aliases {
243            index.register_entry(alias, entry.clone());
244        }
245    }
246}
247
248/// Generate `.Rd` documentation files for all documented builtins.
249///
250/// Creates one `.Rd` file per primary builtin name in the given directory.
251/// Related functions that share aliases are grouped into a single `.Rd` file.
252/// Returns the number of files written.
253pub fn generate_rd_docs(dir: &std::path::Path) -> Result<usize, std::io::Error> {
254    use crate::interpreter::packages::rd::RdDoc;
255    use std::collections::{HashMap, HashSet};
256
257    // First pass: build RdDocs for each builtin, keyed by primary name.
258    // Group aliases so that e.g. paste and paste0 share a page if they
259    // have the same primary descriptor.
260    let mut docs: HashMap<String, RdDoc> = HashMap::new();
261    let mut seen_aliases: HashSet<String> = HashSet::new();
262
263    for desc in BUILTIN_REGISTRY {
264        if desc.doc.is_empty() {
265            continue;
266        }
267
268        // Skip if this descriptor's name was already claimed as an alias
269        // of a previously processed builtin.
270        if seen_aliases.contains(desc.name) {
271            continue;
272        }
273
274        let mut title = None;
275        let mut description_lines = Vec::new();
276        let mut arguments = Vec::new();
277        let mut return_val = None;
278        let mut in_description = false;
279
280        for line in desc.doc.lines() {
281            let line = line.trim();
282            if let Some(rest) = line.strip_prefix("@param ") {
283                in_description = false;
284                if let Some((param, param_desc)) = rest.split_once(' ') {
285                    arguments.push((param.to_string(), param_desc.to_string()));
286                }
287            } else if let Some(ret) = line.strip_prefix("@return ") {
288                in_description = false;
289                return_val = Some(ret.to_string());
290            } else if line.starts_with('@') {
291                in_description = false;
292            } else if title.is_none() && !line.is_empty() {
293                title = Some(line.to_string());
294                in_description = true;
295            } else if in_description && !line.is_empty() {
296                description_lines.push(line.to_string());
297            } else if line.is_empty() && in_description && !description_lines.is_empty() {
298                description_lines.push(String::new());
299            }
300        }
301
302        let params = extract_param_names_from_doc(desc.doc);
303        let usage = if params.is_empty() {
304            format!("{}(...)", desc.name)
305        } else {
306            format!("{}({})", desc.name, params.join(", "))
307        };
308
309        let description = if description_lines.is_empty() {
310            None
311        } else {
312            Some(description_lines.join("\n").trim_end().to_string())
313        };
314
315        let mut aliases: Vec<String> = vec![desc.name.to_string()];
316        for a in desc.aliases {
317            if !aliases.contains(&a.to_string()) {
318                aliases.push(a.to_string());
319            }
320            seen_aliases.insert(a.to_string());
321        }
322        seen_aliases.insert(desc.name.to_string());
323
324        let doc = RdDoc {
325            name: Some(desc.name.to_string()),
326            aliases,
327            title,
328            description,
329            usage: Some(usage),
330            arguments,
331            value: return_val,
332            keywords: if desc.namespace.is_empty() {
333                vec![]
334            } else {
335                vec![desc.namespace.to_string()]
336            },
337            ..Default::default()
338        };
339
340        let safe_name = sanitize_rd_filename(desc.name);
341        docs.entry(safe_name).or_insert(doc);
342    }
343
344    // Write each doc to a file.
345    // Track filenames case-insensitively to avoid collisions on macOS HFS+/APFS.
346    std::fs::create_dir_all(dir)?;
347
348    let mut written: HashSet<String> = HashSet::new();
349    let mut count = 0usize;
350    for (safe_name, doc) in &docs {
351        let lower = safe_name.to_lowercase();
352        if written.contains(&lower) {
353            continue;
354        }
355        let rd_content = doc.to_rd();
356        let file_path = dir.join(format!("{safe_name}.Rd"));
357        std::fs::write(&file_path, rd_content)?;
358        written.insert(lower);
359        count += 1;
360    }
361
362    Ok(count)
363}
364
365/// Sanitize a builtin name for use as a filename.
366///
367/// Operator builtins like `+`, `/`, `<` contain characters that are not safe
368/// in filenames (especially `/` which is a path separator). This replaces
369/// unsafe characters with descriptive names.
370fn sanitize_rd_filename(name: &str) -> String {
371    if name
372        .chars()
373        .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
374    {
375        return name.to_string();
376    }
377
378    let mut out = String::new();
379    for ch in name.chars() {
380        match ch {
381            '+' => out.push_str("plus"),
382            '-' => out.push_str("minus"),
383            '*' => out.push_str("star"),
384            '/' => out.push_str("slash"),
385            '^' => out.push_str("caret"),
386            '<' => out.push_str("lt"),
387            '>' => out.push_str("gt"),
388            '=' => out.push_str("eq"),
389            '!' => out.push_str("bang"),
390            '&' => out.push_str("and"),
391            '|' => out.push_str("or"),
392            '%' => out.push_str("pct"),
393            '~' => out.push_str("tilde"),
394            ':' => out.push_str("colon"),
395            '[' => out.push_str("lbracket"),
396            ']' => out.push_str("rbracket"),
397            '(' => out.push_str("lparen"),
398            ')' => out.push_str("rparen"),
399            '{' => out.push_str("lbrace"),
400            '}' => out.push_str("rbrace"),
401            '$' => out.push_str("dollar"),
402            '@' => out.push_str("at"),
403            _ => out.push(ch),
404        }
405    }
406    out
407}
408
409pub fn register_builtins(env: &Environment) {
410    for descriptor in BUILTIN_REGISTRY {
411        register_builtin_binding(env, descriptor.name, *descriptor);
412        for &alias in descriptor.aliases {
413            register_builtin_binding(env, alias, *descriptor);
414        }
415    }
416
417    // Constants
418    env.set(
419        "pi".to_string(),
420        RValue::vec(Vector::Double(vec![Some(std::f64::consts::PI)].into())),
421    );
422    env.set(
423        "T".to_string(),
424        RValue::vec(Vector::Logical(vec![Some(true)].into())),
425    );
426    env.set(
427        "F".to_string(),
428        RValue::vec(Vector::Logical(vec![Some(false)].into())),
429    );
430    env.set(
431        "TRUE".to_string(),
432        RValue::vec(Vector::Logical(vec![Some(true)].into())),
433    );
434    env.set(
435        "FALSE".to_string(),
436        RValue::vec(Vector::Logical(vec![Some(false)].into())),
437    );
438    env.set(
439        "Inf".to_string(),
440        RValue::vec(Vector::Double(vec![Some(f64::INFINITY)].into())),
441    );
442    env.set(
443        "NaN".to_string(),
444        RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())),
445    );
446    env.set(
447        "NA".to_string(),
448        RValue::vec(Vector::Logical(vec![None].into())),
449    );
450    env.set(
451        "NA_integer_".to_string(),
452        RValue::vec(Vector::Integer(vec![None].into())),
453    );
454    env.set(
455        "NA_real_".to_string(),
456        RValue::vec(Vector::Double(vec![None].into())),
457    );
458    env.set(
459        "NA_character_".to_string(),
460        RValue::vec(Vector::Character(vec![None].into())),
461    );
462    env.set(
463        "LETTERS".to_string(),
464        RValue::vec(Vector::Character(
465            (b'A'..=b'Z')
466                .map(|c| Some(String::from(c as char)))
467                .collect::<Vec<_>>()
468                .into(),
469        )),
470    );
471    env.set(
472        "letters".to_string(),
473        RValue::vec(Vector::Character(
474            (b'a'..=b'z')
475                .map(|c| Some(String::from(c as char)))
476                .collect::<Vec<_>>()
477                .into(),
478        )),
479    );
480    env.set(
481        ".Machine".to_string(),
482        RValue::List(RList::new(vec![
483            (
484                Some("integer.max".to_string()),
485                RValue::vec(Vector::Integer(vec![Some(i64::from(i32::MAX))].into())),
486            ),
487            (
488                Some("double.eps".to_string()),
489                RValue::vec(Vector::Double(vec![Some(f64::EPSILON)].into())),
490            ),
491            (
492                Some("double.neg.eps".to_string()),
493                RValue::vec(Vector::Double(vec![Some(f64::EPSILON / 2.0)].into())),
494            ),
495            (
496                Some("double.xmax".to_string()),
497                RValue::vec(Vector::Double(vec![Some(f64::MAX)].into())),
498            ),
499            (
500                Some("double.xmin".to_string()),
501                RValue::vec(Vector::Double(vec![Some(f64::MIN_POSITIVE)].into())),
502            ),
503            (
504                Some("double.digits".to_string()),
505                RValue::vec(Vector::Integer(vec![Some(53)].into())),
506            ),
507            (
508                Some("double.max.exp".to_string()),
509                RValue::vec(Vector::Integer(vec![Some(1024)].into())),
510            ),
511            (
512                Some("double.min.exp".to_string()),
513                RValue::vec(Vector::Integer(vec![Some(-1021)].into())),
514            ),
515            (
516                Some("sizeof.long".to_string()),
517                RValue::vec(Vector::Integer(
518                    vec![Some(
519                        i64::try_from(std::mem::size_of::<std::ffi::c_long>()).unwrap_or(8),
520                    )]
521                    .into(),
522                )),
523            ),
524            (
525                Some("sizeof.longlong".to_string()),
526                RValue::vec(Vector::Integer(
527                    vec![Some(
528                        i64::try_from(std::mem::size_of::<std::ffi::c_longlong>()).unwrap_or(8),
529                    )]
530                    .into(),
531                )),
532            ),
533            (
534                Some("sizeof.pointer".to_string()),
535                RValue::vec(Vector::Integer(
536                    vec![Some(
537                        i64::try_from(std::mem::size_of::<*const u8>()).unwrap_or(8),
538                    )]
539                    .into(),
540                )),
541            ),
542            (
543                Some("longdouble.eps".to_string()),
544                RValue::vec(Vector::Double(vec![Some(f64::EPSILON)].into())),
545            ),
546            (
547                Some("longdouble.max".to_string()),
548                RValue::vec(Vector::Double(vec![Some(f64::MAX)].into())),
549            ),
550            (
551                Some("longdouble.digits".to_string()),
552                RValue::vec(Vector::Integer(vec![Some(53)].into())),
553            ),
554        ])),
555    );
556
557    // .Platform constant
558    let os_type = if cfg!(unix) { "unix" } else { "windows" };
559    let file_sep = if cfg!(windows) { "\\" } else { "/" };
560    let path_sep = if cfg!(windows) { ";" } else { ":" };
561    let dynlib_ext = if cfg!(target_os = "macos") {
562        ".dylib"
563    } else if cfg!(windows) {
564        ".dll"
565    } else {
566        ".so"
567    };
568    env.set(
569        ".Platform".to_string(),
570        RValue::List(RList::new(vec![
571            (
572                Some("OS.type".to_string()),
573                RValue::vec(Vector::Character(vec![Some(os_type.to_string())].into())),
574            ),
575            (
576                Some("file.sep".to_string()),
577                RValue::vec(Vector::Character(vec![Some(file_sep.to_string())].into())),
578            ),
579            (
580                Some("path.sep".to_string()),
581                RValue::vec(Vector::Character(vec![Some(path_sep.to_string())].into())),
582            ),
583            (
584                Some("dynlib.ext".to_string()),
585                RValue::vec(Vector::Character(vec![Some(dynlib_ext.to_string())].into())),
586            ),
587            (
588                Some("pkgType".to_string()),
589                RValue::vec(Vector::Character(vec![Some("source".to_string())].into())),
590            ),
591        ])),
592    );
593
594    // R.version — version info as a named list (variable, not function)
595    env.set(
596        "R.version".to_string(),
597        RValue::List(RList::new(vec![
598            (
599                Some("major".to_string()),
600                RValue::vec(Vector::Character(vec![Some("4".to_string())].into())),
601            ),
602            (
603                Some("minor".to_string()),
604                RValue::vec(Vector::Character(vec![Some("4.0".to_string())].into())),
605            ),
606            (
607                Some("version.string".to_string()),
608                RValue::vec(Vector::Character(
609                    vec![Some("miniR version 0.1.0 (R-compat 4.4.0)".to_string())].into(),
610                )),
611            ),
612        ])),
613    );
614    env.set(
615        "R.version.string".to_string(),
616        RValue::vec(Vector::Character(
617            vec![Some("miniR version 0.1.0 (R-compat 4.4.0)".to_string())].into(),
618        )),
619    );
620
621    // Primitive operators as callable functions (e.g. lapply(x, `[[`, "name"))
622    register_operator_builtins(env);
623}
624
625/// Register R's primitive operators as first-class callable functions.
626///
627/// In R, operators like `[[`, `[`, `+`, `-` etc. can be passed as function
628/// arguments using backtick-quoting: `lapply(x, \`[[\`, "name")`.
629fn register_operator_builtins(env: &Environment) {
630    use crate::interpreter::value::*;
631
632    // `[[` — extract single element
633    env.set(
634        "[[".to_string(),
635        RValue::Function(RFunction::Builtin {
636            name: "[[".to_string(),
637            implementation: BuiltinImplementation::Eager(|args, _| {
638                if args.len() < 2 {
639                    return Err(RError::new(
640                        RErrorKind::Argument,
641                        "`[[` requires 2 arguments".to_string(),
642                    ));
643                }
644                let obj = &args[0];
645                let idx = &args[1];
646                match obj {
647                    RValue::List(list) => match idx {
648                        RValue::Vector(rv) if matches!(rv.inner, Vector::Character(_)) => {
649                            let name = rv.inner.as_character_scalar().unwrap_or_default();
650                            for (n, v) in &list.values {
651                                if n.as_deref() == Some(&name) {
652                                    return Ok(v.clone());
653                                }
654                            }
655                            Ok(RValue::Null)
656                        }
657                        RValue::Vector(v) => {
658                            let i = v.as_integer_scalar().unwrap_or(0) as usize;
659                            if i > 0 && i <= list.values.len() {
660                                Ok(list.values[i - 1].1.clone())
661                            } else {
662                                Ok(RValue::Null)
663                            }
664                        }
665                        _ => Ok(RValue::Null),
666                    },
667                    RValue::Vector(v) => match idx {
668                        RValue::Vector(idx_rv)
669                            if matches!(idx_rv.inner, Vector::Character(_)) =>
670                        {
671                            let name = idx_rv.inner.as_character_scalar().unwrap_or_default();
672                            if let Some(names_attr) = v.get_attr("names") {
673                                if let Some(names_vec) = names_attr.as_vector() {
674                                    let name_strs = names_vec.to_characters();
675                                    for (j, n) in name_strs.iter().enumerate() {
676                                        if n.as_deref() == Some(name.as_str()) && j < v.len() {
677                                            return Ok(
678                                                crate::interpreter::indexing::extract_vector_element(
679                                                    v, j,
680                                                ),
681                                            );
682                                        }
683                                    }
684                                }
685                            }
686                            Ok(RValue::Null)
687                        }
688                        RValue::Vector(idx_rv) => {
689                            let i = idx_rv.as_integer_scalar().unwrap_or(0) as usize;
690                            if i > 0 && i <= v.len() {
691                                Ok(crate::interpreter::indexing::extract_vector_element(v, i - 1))
692                            } else {
693                                Ok(RValue::Null)
694                            }
695                        }
696                        _ => Ok(RValue::Null),
697                    },
698                    _ => Err(RError::other("object of type 'closure' is not subsettable".to_string())),
699                }
700            }),
701            min_args: 2,
702            max_args: None,
703            formals: &[],
704        }),
705    );
706
707    // `[` — subset
708    env.set(
709        "[".to_string(),
710        RValue::Function(RFunction::Builtin {
711            name: "[".to_string(),
712            implementation: BuiltinImplementation::Eager(|args, _| {
713                if args.is_empty() {
714                    return Err(RError::new(
715                        RErrorKind::Argument,
716                        "`[` requires at least 1 argument".to_string(),
717                    ));
718                }
719                if args.len() == 1 {
720                    return Ok(args[0].clone());
721                }
722                // Delegate to the indexing machinery via a simplified path
723                // For now, support the common case: vector[index]
724                match (&args[0], &args[1]) {
725                    (RValue::Vector(v), RValue::Vector(idx)) => {
726                        let i = idx.as_integer_scalar().unwrap_or(0) as usize;
727                        if i > 0 && i <= v.len() {
728                            Ok(crate::interpreter::indexing::extract_vector_element(
729                                v,
730                                i - 1,
731                            ))
732                        } else {
733                            Ok(RValue::Null)
734                        }
735                    }
736                    (RValue::List(list), RValue::Vector(idx)) => {
737                        let i = idx.as_integer_scalar().unwrap_or(0) as usize;
738                        if i > 0 && i <= list.values.len() {
739                            let (name, val) = &list.values[i - 1];
740                            Ok(RValue::List(RList::new(vec![(name.clone(), val.clone())])))
741                        } else {
742                            Ok(RValue::Null)
743                        }
744                    }
745                    _ => Ok(RValue::Null),
746                }
747            }),
748            min_args: 1,
749            max_args: None,
750            formals: &[],
751        }),
752    );
753}
754
755#[cfg(test)]
756mod tests {
757    use std::collections::HashSet;
758
759    use super::BUILTIN_REGISTRY;
760
761    #[test]
762    fn builtin_registry_names_are_unique() {
763        let mut names = HashSet::new();
764
765        for descriptor in BUILTIN_REGISTRY {
766            assert!(
767                names.insert(descriptor.name),
768                "duplicate builtin name or alias in registry: {}",
769                descriptor.name
770            );
771            for &alias in descriptor.aliases {
772                assert!(
773                    names.insert(alias),
774                    "duplicate builtin name or alias in registry: {}",
775                    alias
776                );
777            }
778        }
779    }
780}
781
782// === Builtin implementations ===
783
784/// Combine values into a vector or list.
785///
786/// Coerces all arguments to a common type and concatenates them.
787/// Named arguments become element names on the result.
788///
789/// @param ... values to combine
790/// @return vector or list containing all input elements
791#[builtin]
792pub fn builtin_c(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
793    // Collect (optional_name, value) pairs
794    let mut all_entries: Vec<(Option<String>, RValue)> = Vec::new();
795    for arg in args {
796        all_entries.push((None, arg.clone()));
797    }
798    for (name, val) in named {
799        all_entries.push((Some(name.clone()), val.clone()));
800    }
801
802    if all_entries.is_empty() {
803        return Ok(RValue::Null);
804    }
805
806    let all_values: Vec<RValue> = all_entries.iter().map(|(_, v)| v.clone()).collect();
807
808    // Check if any are lists
809    let has_list = all_values.iter().any(|v| matches!(v, RValue::List(_)));
810    if has_list {
811        let mut result = Vec::new();
812        for val in &all_values {
813            match val {
814                RValue::List(l) => result.extend(l.values.clone()),
815                RValue::Null => {}
816                other => result.push((None, other.clone())),
817            }
818        }
819        return Ok(RValue::List(RList::new(result)));
820    }
821
822    // Determine highest type (raw < logical < integer < double < complex < character)
823    let mut has_char = false;
824    let mut has_complex = false;
825    let mut has_double = false;
826    let mut has_int = false;
827    let mut has_logical = false;
828    let mut has_raw = false;
829
830    for val in &all_values {
831        match val {
832            RValue::Vector(rv) if matches!(rv.inner, Vector::Character(_)) => has_char = true,
833            RValue::Vector(rv) if matches!(rv.inner, Vector::Complex(_)) => has_complex = true,
834            RValue::Vector(rv) if matches!(rv.inner, Vector::Double(_)) => has_double = true,
835            RValue::Vector(rv) if matches!(rv.inner, Vector::Integer(_)) => has_int = true,
836            RValue::Vector(rv) if matches!(rv.inner, Vector::Logical(_)) => has_logical = true,
837            RValue::Vector(rv) if matches!(rv.inner, Vector::Raw(_)) => has_raw = true,
838            RValue::Null => {}
839            _ => {}
840        }
841    }
842
843    let vec_result = if has_char {
844        let mut result = Vec::new();
845        for val in &all_values {
846            match val {
847                RValue::Vector(v) => result.extend(v.to_characters()),
848                RValue::Null => {}
849                _ => {}
850            }
851        }
852        RValue::vec(Vector::Character(result.into()))
853    } else if has_complex {
854        let mut result = Vec::new();
855        for val in &all_values {
856            match val {
857                RValue::Vector(v) => result.extend(v.inner.to_complex()),
858                RValue::Null => {}
859                _ => {}
860            }
861        }
862        RValue::vec(Vector::Complex(result.into()))
863    } else if has_double {
864        let mut result = Vec::new();
865        for val in &all_values {
866            match val {
867                RValue::Vector(v) => result.extend(v.to_doubles()),
868                RValue::Null => {}
869                _ => {}
870            }
871        }
872        RValue::vec(Vector::Double(result.into()))
873    } else if has_int {
874        let mut result = Vec::new();
875        for val in &all_values {
876            match val {
877                RValue::Vector(v) => result.extend(v.to_integers()),
878                RValue::Null => {}
879                _ => {}
880            }
881        }
882        RValue::vec(Vector::Integer(result.into()))
883    } else if has_logical || !has_raw {
884        let mut result = Vec::new();
885        for val in &all_values {
886            match val {
887                RValue::Vector(v) => result.extend(v.to_logicals()),
888                RValue::Null => {}
889                _ => {}
890            }
891        }
892        RValue::vec(Vector::Logical(result.into()))
893    } else {
894        let mut result = Vec::new();
895        for val in &all_values {
896            match val {
897                RValue::Vector(v) => result.extend(v.inner.to_raw()),
898                RValue::Null => {}
899                _ => {}
900            }
901        }
902        RValue::vec(Vector::Raw(result))
903    };
904
905    // Collect names from named arguments and existing names attributes
906    let result = vec_result;
907    let names = collect_c_names(&all_entries);
908    if names.iter().any(|n| n.is_some()) {
909        match result {
910            RValue::Vector(mut rv) => {
911                rv.set_attr(
912                    "names".to_string(),
913                    RValue::vec(Vector::Character(names.into())),
914                );
915                Ok(RValue::Vector(rv))
916            }
917            other => Ok(other),
918        }
919    } else {
920        Ok(result)
921    }
922}
923
924/// Collect element names for c(): named arguments provide names for scalars,
925/// existing names attributes provide names for vector elements.
926fn collect_c_names(entries: &[(Option<String>, RValue)]) -> Vec<Option<String>> {
927    let mut names = Vec::new();
928    for (arg_name, val) in entries {
929        match val {
930            RValue::Vector(rv) => {
931                let len = rv.inner.len();
932                // Check for existing names attribute
933                let existing_names = rv
934                    .get_attr("names")
935                    .and_then(|v| v.as_vector())
936                    .map(|v| v.to_characters());
937
938                if let Some(ref enames) = existing_names {
939                    for i in 0..len {
940                        names.push(enames.get(i).cloned().flatten());
941                    }
942                } else if len == 1 {
943                    // Scalar: use the argument name if present
944                    names.push(arg_name.clone());
945                } else {
946                    // Multi-element unnamed vector
947                    for _ in 0..len {
948                        names.push(None);
949                    }
950                }
951            }
952            RValue::Null => {}
953            _ => names.push(arg_name.clone()),
954        }
955    }
956    names
957}
958
959/// Display help for a function.
960///
961/// @param topic name of the function to look up
962/// @param package restrict search to this package (used by binary `?`, e.g. `methods?show`)
963#[interpreter_builtin(min_args = 1)]
964fn interp_help(
965    args: &[RValue],
966    named: &[(String, RValue)],
967    context: &BuiltinContext,
968) -> Result<RValue, RError> {
969    let name = match args.first() {
970        Some(RValue::Vector(rv)) => rv.as_character_scalar().unwrap_or_default(),
971        Some(RValue::Function(RFunction::Builtin { name, .. })) => name.clone(),
972        _ => String::new(),
973    };
974    if name.is_empty() {
975        return Ok(RValue::Null);
976    }
977
978    // If package= is specified (from binary ? like methods?show), qualify the lookup
979    let pkg = named.iter().find_map(|(k, v)| {
980        if k == "package" {
981            if let RValue::Vector(rv) = v {
982                rv.as_character_scalar()
983            } else {
984                None
985            }
986        } else {
987            None
988        }
989    });
990    let name = if let Some(ref pkg) = pkg {
991        format!("{pkg}::{name}")
992    } else {
993        name
994    };
995
996    // Check if it's a namespace name (e.g. ?base, ?stats, ?utils)
997    if BUILTIN_REGISTRY.iter().any(|d| d.namespace == name) {
998        let mut fns: Vec<&str> = BUILTIN_REGISTRY
999            .iter()
1000            .filter(|d| d.namespace == name)
1001            .map(|d| d.name)
1002            .collect();
1003        fns.sort();
1004        fns.dedup();
1005        println!("Package '{name}'");
1006        println!("{}", "─".repeat(20 + name.len()));
1007        println!();
1008        println!("{} functions:", fns.len());
1009        println!();
1010        let max_width = fns.iter().map(|f| f.len()).max().unwrap_or(10) + 2;
1011        let cols = 80 / max_width.max(1);
1012        for (i, f) in fns.iter().enumerate() {
1013            print!("{:<width$}", f, width = max_width);
1014            if (i + 1) % cols == 0 {
1015                println!();
1016            }
1017        }
1018        if !fns.len().is_multiple_of(cols) {
1019            println!();
1020        }
1021        println!();
1022        println!("Use ?{name}::name for help on a specific function.");
1023        return Ok(RValue::Null);
1024    }
1025
1026    // 1. Check Rd help index (package man/ pages) first
1027    let rd_result = {
1028        let index = context.interpreter().rd_help_index.borrow();
1029        if let Some((ns, n)) = name.split_once("::") {
1030            index.lookup_in_package(n, ns).map(|e| e.doc.format_text())
1031        } else {
1032            index.lookup(&name).first().map(|e| e.doc.format_text())
1033        }
1034    };
1035    if let Some(text) = rd_result {
1036        context.write(&text);
1037        context.write("\n");
1038        return Ok(RValue::Null);
1039    }
1040
1041    // 2. Fall back to builtin registry (rustdoc-based help)
1042    let descriptor = if let Some((ns, n)) = name.split_once("::") {
1043        find_builtin_ns(ns, n)
1044    } else {
1045        find_builtin(&name)
1046    };
1047    match descriptor {
1048        Some(d) => {
1049            context.write(&format!("{}\n", format_help(d)));
1050            Ok(RValue::Null)
1051        }
1052        None => {
1053            context.write(&format!("No documentation for '{name}'\n"));
1054            Ok(RValue::Null)
1055        }
1056    }
1057}
1058
1059// print() is in interp.rs (S3-dispatching interpreter builtin)
1060
1061/// Concatenate and print objects to stdout.
1062///
1063/// Converts each argument to character and writes them separated by `sep`.
1064/// Unlike `print()`, does not add a trailing newline unless the output contains one.
1065/// When `file` is specified, writes to that file instead of stdout. When `append`
1066/// is TRUE, appends to the file rather than overwriting.
1067///
1068/// @param ... objects to concatenate and print
1069/// @param sep separator string between elements (default: " ")
1070/// @param file a connection or file name to write to (default: "" meaning stdout)
1071/// @param append logical; if TRUE, append to file (default: FALSE)
1072/// @return NULL (invisibly)
1073#[interpreter_builtin(name = "cat")]
1074fn builtin_cat(
1075    args: &[RValue],
1076    named: &[(String, RValue)],
1077    context: &BuiltinContext,
1078) -> Result<RValue, RError> {
1079    let sep = named
1080        .iter()
1081        .find(|(n, _)| n == "sep")
1082        .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
1083        .unwrap_or_else(|| " ".to_string());
1084
1085    let file = named
1086        .iter()
1087        .find(|(n, _)| n == "file")
1088        .and_then(|(_, v)| v.as_vector()?.as_character_scalar());
1089
1090    let append = named
1091        .iter()
1092        .find(|(n, _)| n == "append")
1093        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
1094        .unwrap_or(false);
1095
1096    let parts: Vec<String> = args
1097        .iter()
1098        .map(|arg| match arg {
1099            RValue::Vector(v) => {
1100                let elems: Vec<String> = match &v.inner {
1101                    Vector::Raw(vals) => vals.iter().map(|b| format!("{:02x}", b)).collect(),
1102                    Vector::Character(vals) => vals
1103                        .iter()
1104                        .map(|x| x.clone().unwrap_or_else(|| "NA".to_string()))
1105                        .collect(),
1106                    Vector::Double(vals) => vals
1107                        .iter_opt()
1108                        .map(|x| x.map(format_r_double).unwrap_or_else(|| "NA".to_string()))
1109                        .collect(),
1110                    Vector::Integer(vals) => vals
1111                        .iter_opt()
1112                        .map(|x| x.map(|i| i.to_string()).unwrap_or_else(|| "NA".to_string()))
1113                        .collect(),
1114                    Vector::Logical(vals) => vals
1115                        .iter()
1116                        .map(|x| match x {
1117                            Some(true) => "TRUE".to_string(),
1118                            Some(false) => "FALSE".to_string(),
1119                            None => "NA".to_string(),
1120                        })
1121                        .collect(),
1122                    Vector::Complex(vals) => vals
1123                        .iter()
1124                        .map(|x| x.map(format_r_complex).unwrap_or_else(|| "NA".to_string()))
1125                        .collect(),
1126                };
1127                elems.join(&sep)
1128            }
1129            RValue::Null => "".to_string(),
1130            other => format!("{}", other),
1131        })
1132        .collect();
1133
1134    let output = parts.join(&sep);
1135
1136    match file {
1137        Some(ref path) if !path.is_empty() => {
1138            use std::fs::OpenOptions;
1139            use std::io::Write;
1140            let path = context.interpreter().resolve_path(path);
1141            let mut f = OpenOptions::new()
1142                .write(true)
1143                .create(true)
1144                .append(append)
1145                .truncate(!append)
1146                .open(&path)
1147                .map_err(|e| {
1148                    RError::other(format!("cannot open file '{}': {}", path.display(), e))
1149                })?;
1150            f.write_all(output.as_bytes()).map_err(|e| {
1151                RError::other(format!("error writing to '{}': {}", path.display(), e))
1152            })?;
1153        }
1154        _ => {
1155            context.write(&output);
1156        }
1157    }
1158
1159    Ok(RValue::Null)
1160}
1161
1162/// Concatenate strings with a separator.
1163///
1164/// Converts arguments to character, recycles to the longest length,
1165/// and joins corresponding elements with `sep`. Optionally collapses
1166/// the result into a single string using `collapse`.
1167///
1168/// @param ... objects to paste together
1169/// @param sep separator between arguments (default: " ")
1170/// @param collapse optional string to join result elements
1171/// @return character vector (length 1 if collapse is set)
1172#[builtin]
1173fn builtin_paste(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1174    let sep = named
1175        .iter()
1176        .find(|(n, _)| n == "sep")
1177        .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
1178        .unwrap_or_else(|| " ".to_string());
1179    let collapse = named
1180        .iter()
1181        .find(|(n, _)| n == "collapse")
1182        .and_then(|(_, v)| v.as_vector()?.as_character_scalar());
1183
1184    // Convert each arg to character vector
1185    let char_vecs: Vec<Vec<String>> = args
1186        .iter()
1187        .map(|arg| match arg {
1188            RValue::Vector(v) => v
1189                .to_characters()
1190                .into_iter()
1191                .map(|s| s.unwrap_or_else(|| "NA".to_string()))
1192                .collect(),
1193            RValue::Null => vec![],
1194            other => vec![format!("{}", other)],
1195        })
1196        .collect();
1197
1198    if char_vecs.is_empty() {
1199        return Ok(RValue::vec(Vector::Character(
1200            vec![Some(String::new())].into(),
1201        )));
1202    }
1203
1204    // Recycle to max length
1205    let max_len = char_vecs.iter().map(|v| v.len()).max().unwrap_or(0);
1206    if max_len == 0 {
1207        return Ok(RValue::vec(Vector::Character(vec![].into())));
1208    }
1209
1210    let result: Vec<Option<String>> = (0..max_len)
1211        .map(|i| {
1212            let parts: Vec<&str> = char_vecs
1213                .iter()
1214                .filter(|v| !v.is_empty())
1215                .map(|v| v[i % v.len()].as_str())
1216                .collect();
1217            Some(parts.join(&sep))
1218        })
1219        .collect();
1220
1221    match collapse {
1222        Some(col) => {
1223            let collapsed: String = result.iter().filter_map(|s| s.as_ref()).join(&col);
1224            Ok(RValue::vec(Vector::Character(vec![Some(collapsed)].into())))
1225        }
1226        None => Ok(RValue::vec(Vector::Character(result.into()))),
1227    }
1228}
1229
1230/// Concatenate strings with no separator.
1231///
1232/// Equivalent to `paste(..., sep = "")`.
1233///
1234/// @param ... objects to paste together
1235/// @param collapse optional string to join result elements
1236/// @return character vector
1237#[builtin]
1238fn builtin_paste0(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1239    let mut new_named = named.to_vec();
1240    if !new_named.iter().any(|(n, _)| n == "sep") {
1241        new_named.push((
1242            "sep".to_string(),
1243            RValue::vec(Vector::Character(vec![Some(String::new())].into())),
1244        ));
1245    }
1246    builtin_paste(args, &new_named)
1247}
1248
1249/// Get the length of an object.
1250///
1251/// @param x object to measure
1252/// @return integer scalar
1253#[builtin(min_args = 1)]
1254fn builtin_length(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
1255    let len = args.first().map(|v| v.length()).unwrap_or(0);
1256    Ok(RValue::vec(Vector::Integer(
1257        vec![Some(i64::try_from(len)?)].into(),
1258    )))
1259}
1260
1261/// Count the number of characters in each element of a character vector.
1262///
1263/// @param x character vector
1264/// @param type one of "bytes", "chars", "width", or "graphemes" (default "chars")
1265/// @return integer vector of string lengths
1266#[builtin(min_args = 1)]
1267fn builtin_nchar(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1268    use unicode_segmentation::UnicodeSegmentation;
1269
1270    // Extract the "type" named argument (default: "chars")
1271    let nchar_type = named
1272        .iter()
1273        .find(|(k, _)| k == "type")
1274        .and_then(|(_, v)| match v {
1275            RValue::Vector(rv) => rv.inner.as_character_scalar(),
1276            _ => None,
1277        })
1278        .unwrap_or_else(|| "chars".to_string());
1279
1280    match args.first() {
1281        Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Character(_)) => {
1282            let Vector::Character(vals) = &rv.inner else {
1283                unreachable!()
1284            };
1285            let result: Vec<Option<i64>> = vals
1286                .iter()
1287                .map(|s| {
1288                    s.as_ref().map(|s| {
1289                        let n = match nchar_type.as_str() {
1290                            "bytes" => s.len(),
1291                            "width" => UnicodeWidthStr::width(s.as_str()),
1292                            "graphemes" => s.graphemes(true).count(),
1293                            #[cfg(feature = "bytecount")]
1294                            _ => bytecount::num_chars(s.as_bytes()), // SIMD-accelerated UTF-8 char count
1295                            #[cfg(not(feature = "bytecount"))]
1296                            _ => s.chars().count(), // "chars" or default
1297                        };
1298                        i64::try_from(n).unwrap_or(0)
1299                    })
1300                })
1301                .collect();
1302            Ok(RValue::vec(Vector::Integer(result.into())))
1303        }
1304        _ => Ok(RValue::vec(Vector::Integer(vec![None].into()))),
1305    }
1306}
1307
1308/// Get the names attribute of an object.
1309///
1310/// @param x object whose names to retrieve
1311/// @return character vector of names, or NULL if unnamed
1312#[builtin(min_args = 1)]
1313fn builtin_names(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1314    match args.first() {
1315        Some(RValue::Vector(rv)) => match rv.get_attr("names") {
1316            Some(v) => Ok(v.clone()),
1317            None => Ok(RValue::Null),
1318        },
1319        Some(RValue::List(l)) => Ok(list_names_value(l)),
1320        _ => Ok(RValue::Null),
1321    }
1322}
1323
1324/// Set the names attribute of an object.
1325///
1326/// @param x object to modify
1327/// @param value character vector of new names, or NULL to remove
1328/// @return the modified object
1329#[builtin(name = "names<-", min_args = 2)]
1330fn builtin_names_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1331    let names_val = args.get(1).cloned().unwrap_or(RValue::Null);
1332    match args.first() {
1333        Some(RValue::Vector(rv)) => {
1334            let mut rv = rv.clone();
1335            if names_val.is_null() {
1336                rv.attrs.as_mut().map(|a| a.shift_remove("names"));
1337            } else {
1338                rv.set_attr("names".to_string(), names_val);
1339            }
1340            Ok(RValue::Vector(rv))
1341        }
1342        Some(RValue::List(l)) => {
1343            let mut l = l.clone();
1344            set_list_names(&mut l, &names_val);
1345            Ok(RValue::List(l))
1346        }
1347        other => Ok(other.cloned().unwrap_or(RValue::Null)),
1348    }
1349}
1350
1351fn character_name_vector(values: Vec<Option<String>>) -> RValue {
1352    RValue::vec(Vector::Character(values.into()))
1353}
1354
1355fn coerce_name_strings(value: &RValue) -> RValue {
1356    match value {
1357        RValue::Vector(rv) => match &rv.inner {
1358            Vector::Character(_) => value.clone(),
1359            Vector::Integer(values) => RValue::vec(Vector::Character(
1360                values
1361                    .iter_opt()
1362                    .map(|value| value.map(|value| value.to_string()))
1363                    .collect::<Vec<_>>()
1364                    .into(),
1365            )),
1366            Vector::Double(values) => RValue::vec(Vector::Character(
1367                values
1368                    .iter_opt()
1369                    .map(|value| value.map(format_r_double))
1370                    .collect::<Vec<_>>()
1371                    .into(),
1372            )),
1373            _ => RValue::Null,
1374        },
1375        _ => RValue::Null,
1376    }
1377}
1378
1379fn coerce_name_values(value: &RValue) -> Option<Vec<Option<String>>> {
1380    let coerced = coerce_name_strings(value);
1381    coerced.as_vector().map(|values| values.to_characters())
1382}
1383
1384fn list_names_value(list: &RList) -> RValue {
1385    if let Some(names_attr) = list.get_attr("names") {
1386        return coerce_name_strings(names_attr);
1387    }
1388
1389    let names: Vec<Option<String>> = list.values.iter().map(|(name, _)| name.clone()).collect();
1390    if names.iter().all(|name| name.is_none()) {
1391        RValue::Null
1392    } else {
1393        character_name_vector(names)
1394    }
1395}
1396
1397fn set_list_names(list: &mut RList, names_val: &RValue) {
1398    if let Some(mut names) = coerce_name_values(names_val) {
1399        names.resize(list.values.len(), None);
1400        for (entry, name) in list.values.iter_mut().zip(names.iter()) {
1401            entry.0 = name.clone();
1402        }
1403        list.set_attr("names".to_string(), character_name_vector(names));
1404        return;
1405    }
1406
1407    if names_val.is_null() {
1408        for entry in &mut list.values {
1409            entry.0 = None;
1410        }
1411        list.attrs.as_mut().map(|attrs| attrs.shift_remove("names"));
1412    }
1413}
1414
1415fn data_frame_row_count(list: &RList) -> usize {
1416    list.get_attr("row.names")
1417        .map(RValue::length)
1418        .unwrap_or_else(|| {
1419            list.values
1420                .iter()
1421                .map(|(_, value)| value.length())
1422                .max()
1423                .unwrap_or(0)
1424        })
1425}
1426
1427fn automatic_row_names_value(count: usize) -> Result<RValue, RError> {
1428    Ok(RValue::vec(Vector::Integer(
1429        (1..=i64::try_from(count)?)
1430            .map(Some)
1431            .collect::<Vec<_>>()
1432            .into(),
1433    )))
1434}
1435
1436fn data_frame_dimnames_value(list: &RList) -> Result<RValue, RError> {
1437    let row_names = list
1438        .get_attr("row.names")
1439        .map(coerce_name_strings)
1440        .unwrap_or(automatic_row_names_value(data_frame_row_count(list))?);
1441    let col_names = list_names_value(list);
1442    Ok(RValue::List(RList::new(vec![
1443        (None, row_names),
1444        (None, col_names),
1445    ])))
1446}
1447
1448fn set_data_frame_row_names(list: &mut RList, row_names: &RValue) -> Result<(), RError> {
1449    if row_names.is_null() {
1450        list.set_attr(
1451            "row.names".to_string(),
1452            automatic_row_names_value(data_frame_row_count(list))?,
1453        );
1454        return Ok(());
1455    }
1456
1457    let Some(names) = coerce_name_values(row_names) else {
1458        return Err(RError::other(
1459            "row names supplied are of the wrong length".to_string(),
1460        ));
1461    };
1462    if names.len() != data_frame_row_count(list) {
1463        return Err(RError::other(
1464            "row names supplied are of the wrong length".to_string(),
1465        ));
1466    }
1467    list.set_attr("row.names".to_string(), character_name_vector(names));
1468    Ok(())
1469}
1470
1471fn set_data_frame_col_names(list: &mut RList, col_names: &RValue) -> Result<(), RError> {
1472    if col_names.is_null() {
1473        set_list_names(list, col_names);
1474        return Ok(());
1475    }
1476
1477    let Some(names) = coerce_name_values(col_names) else {
1478        return Err(RError::other(
1479            "'names' attribute [1] must be the same length as the vector [0]".to_string(),
1480        ));
1481    };
1482    if names.len() != list.values.len() {
1483        return Err(RError::other(format!(
1484            "'names' attribute [{}] must be the same length as the vector [{}]",
1485            names.len(),
1486            list.values.len()
1487        )));
1488    }
1489    set_list_names(list, &character_name_vector(names));
1490    Ok(())
1491}
1492
1493fn set_data_frame_dimnames(list: &mut RList, dimnames: &RValue) -> Result<(), RError> {
1494    let RValue::List(values) = dimnames else {
1495        return Err(RError::other(
1496            "invalid 'dimnames' given for data frame".to_string(),
1497        ));
1498    };
1499    if values.values.len() != 2 {
1500        return Err(RError::other(
1501            "invalid 'dimnames' given for data frame".to_string(),
1502        ));
1503    }
1504
1505    let row_names = &values.values[0].1;
1506    let col_names = &values.values[1].1;
1507
1508    let Some(row_values) = coerce_name_values(row_names) else {
1509        return Err(RError::other(
1510            "invalid 'dimnames' given for data frame".to_string(),
1511        ));
1512    };
1513    let Some(col_values) = coerce_name_values(col_names) else {
1514        return Err(RError::other(
1515            "invalid 'dimnames' given for data frame".to_string(),
1516        ));
1517    };
1518
1519    if row_values.len() != data_frame_row_count(list) || col_values.len() != list.values.len() {
1520        return Err(RError::other(
1521            "invalid 'dimnames' given for data frame".to_string(),
1522        ));
1523    }
1524
1525    list.set_attr("row.names".to_string(), character_name_vector(row_values));
1526    set_list_names(list, &character_name_vector(col_values));
1527    list.attrs
1528        .as_mut()
1529        .map(|attrs| attrs.shift_remove("dimnames"));
1530    Ok(())
1531}
1532
1533fn updated_dimnames_component(current: Option<&RValue>, index: usize, value: &RValue) -> RValue {
1534    let mut components = match current {
1535        Some(RValue::List(list)) => {
1536            let mut components: Vec<RValue> =
1537                list.values.iter().map(|(_, value)| value.clone()).collect();
1538            components.resize(2, RValue::Null);
1539            components
1540        }
1541        _ => vec![RValue::Null, RValue::Null],
1542    };
1543
1544    if index < components.len() {
1545        components[index] = value.clone();
1546    }
1547
1548    if components.iter().all(RValue::is_null) {
1549        RValue::Null
1550    } else {
1551        RValue::List(RList::new(
1552            components.into_iter().map(|value| (None, value)).collect(),
1553        ))
1554    }
1555}
1556
1557struct BindInput {
1558    data: Vec<Option<f64>>,
1559    nrow: usize,
1560    ncol: usize,
1561    row_names: Option<Vec<Option<String>>>,
1562    col_names: Option<Vec<Option<String>>>,
1563}
1564
1565fn dimnames_component(dimnames: Option<&RValue>, index: usize) -> Option<Vec<Option<String>>> {
1566    let Some(RValue::List(list)) = dimnames else {
1567        return None;
1568    };
1569    list.values
1570        .get(index)
1571        .and_then(|(_, value)| coerce_name_values(value))
1572}
1573
1574fn bind_dimnames_value(
1575    row_names: Vec<Option<String>>,
1576    col_names: Vec<Option<String>>,
1577) -> Option<RValue> {
1578    let has_row_names = row_names.iter().any(|name| name.is_some());
1579    let has_col_names = col_names.iter().any(|name| name.is_some());
1580    if !has_row_names && !has_col_names {
1581        return None;
1582    }
1583
1584    Some(RValue::List(RList::new(vec![
1585        (None, character_name_vector(row_names)),
1586        (None, character_name_vector(col_names)),
1587    ])))
1588}
1589
1590/// Get the row names of a data frame or matrix.
1591///
1592/// @param x data frame, matrix, or object with dimnames
1593/// @return character vector of row names, or NULL
1594#[builtin(name = "row.names", names = ["rownames"], min_args = 1)]
1595fn builtin_row_names(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1596    match args.first() {
1597        Some(RValue::List(list)) => Ok(list
1598            .get_attr("row.names")
1599            .map(coerce_name_strings)
1600            .unwrap_or(RValue::Null)),
1601        Some(RValue::Vector(rv)) => {
1602            if let Some(RValue::List(dimnames)) = rv.get_attr("dimnames") {
1603                if let Some((_, row_names)) = dimnames.values.first() {
1604                    return Ok(coerce_name_strings(row_names));
1605                }
1606            }
1607            Ok(RValue::Null)
1608        }
1609        _ => Ok(RValue::Null),
1610    }
1611}
1612
1613/// Get the column names of a data frame or matrix.
1614///
1615/// @param x data frame, matrix, or object with dimnames
1616/// @return character vector of column names, or NULL
1617#[builtin(name = "colnames", min_args = 1)]
1618fn builtin_col_names(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1619    match args.first() {
1620        Some(value @ RValue::List(list)) => {
1621            if has_class(value, "data.frame") {
1622                return Ok(list_names_value(list));
1623            }
1624            if let Some(RValue::List(dimnames)) = list.get_attr("dimnames") {
1625                if let Some((_, col_names)) = dimnames.values.get(1) {
1626                    return Ok(coerce_name_strings(col_names));
1627                }
1628            }
1629            Ok(RValue::Null)
1630        }
1631        Some(RValue::Vector(rv)) => {
1632            if let Some(RValue::List(dimnames)) = rv.get_attr("dimnames") {
1633                if let Some((_, col_names)) = dimnames.values.get(1) {
1634                    return Ok(coerce_name_strings(col_names));
1635                }
1636            }
1637            Ok(RValue::Null)
1638        }
1639        _ => Ok(RValue::Null),
1640    }
1641}
1642
1643/// Set the row names of a data frame or matrix.
1644///
1645/// @param x data frame, matrix, or object with dimnames
1646/// @param value character vector of new row names, or NULL
1647/// @return the modified object
1648#[builtin(name = "rownames<-", names = ["row.names<-"], min_args = 2)]
1649fn builtin_row_names_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1650    let row_names = args.get(1).cloned().unwrap_or(RValue::Null);
1651    match args.first() {
1652        Some(value @ RValue::List(list)) if has_class(value, "data.frame") => {
1653            let mut list = list.clone();
1654            set_data_frame_row_names(&mut list, &row_names)?;
1655            Ok(RValue::List(list))
1656        }
1657        Some(RValue::Vector(rv)) => {
1658            let mut rv = rv.clone();
1659            let dimnames = updated_dimnames_component(rv.get_attr("dimnames"), 0, &row_names);
1660            if dimnames.is_null() {
1661                rv.attrs
1662                    .as_mut()
1663                    .map(|attrs| attrs.shift_remove("dimnames"));
1664            } else {
1665                rv.set_attr("dimnames".to_string(), dimnames);
1666            }
1667            Ok(RValue::Vector(rv))
1668        }
1669        Some(RValue::List(list)) => {
1670            let mut list = list.clone();
1671            let dimnames = updated_dimnames_component(list.get_attr("dimnames"), 0, &row_names);
1672            if dimnames.is_null() {
1673                list.attrs
1674                    .as_mut()
1675                    .map(|attrs| attrs.shift_remove("dimnames"));
1676            } else {
1677                list.set_attr("dimnames".to_string(), dimnames);
1678            }
1679            Ok(RValue::List(list))
1680        }
1681        other => Ok(other.cloned().unwrap_or(RValue::Null)),
1682    }
1683}
1684
1685/// Set the column names of a data frame or matrix.
1686///
1687/// @param x data frame, matrix, or object with dimnames
1688/// @param value character vector of new column names, or NULL
1689/// @return the modified object
1690#[builtin(name = "colnames<-", min_args = 2)]
1691fn builtin_col_names_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1692    let col_names = args.get(1).cloned().unwrap_or(RValue::Null);
1693    match args.first() {
1694        Some(value @ RValue::List(list)) if has_class(value, "data.frame") => {
1695            let mut list = list.clone();
1696            set_data_frame_col_names(&mut list, &col_names)?;
1697            Ok(RValue::List(list))
1698        }
1699        Some(RValue::Vector(rv)) => {
1700            let mut rv = rv.clone();
1701            let dimnames = updated_dimnames_component(rv.get_attr("dimnames"), 1, &col_names);
1702            if dimnames.is_null() {
1703                rv.attrs
1704                    .as_mut()
1705                    .map(|attrs| attrs.shift_remove("dimnames"));
1706            } else {
1707                rv.set_attr("dimnames".to_string(), dimnames);
1708            }
1709            Ok(RValue::Vector(rv))
1710        }
1711        Some(RValue::List(list)) => {
1712            let mut list = list.clone();
1713            let dimnames = updated_dimnames_component(list.get_attr("dimnames"), 1, &col_names);
1714            if dimnames.is_null() {
1715                list.attrs
1716                    .as_mut()
1717                    .map(|attrs| attrs.shift_remove("dimnames"));
1718            } else {
1719                list.set_attr("dimnames".to_string(), dimnames);
1720            }
1721            Ok(RValue::List(list))
1722        }
1723        other => Ok(other.cloned().unwrap_or(RValue::Null)),
1724    }
1725}
1726
1727/// Set the class attribute of an object.
1728///
1729/// @param x object to modify
1730/// @param value character vector of class names, or NULL to remove
1731/// @return the modified object
1732#[builtin(name = "class<-", min_args = 2)]
1733fn builtin_class_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1734    let class_val = args.get(1).cloned().unwrap_or(RValue::Null);
1735    match args.first() {
1736        Some(RValue::Vector(rv)) => {
1737            let mut rv = rv.clone();
1738            if class_val.is_null() {
1739                rv.attrs.as_mut().map(|a| a.shift_remove("class"));
1740            } else {
1741                rv.set_attr("class".to_string(), class_val);
1742            }
1743            Ok(RValue::Vector(rv))
1744        }
1745        Some(RValue::List(l)) => {
1746            let mut l = l.clone();
1747            if class_val.is_null() {
1748                l.attrs.as_mut().map(|a| a.shift_remove("class"));
1749            } else {
1750                l.set_attr("class".to_string(), class_val);
1751            }
1752            Ok(RValue::List(l))
1753        }
1754        Some(RValue::Language(lang)) => {
1755            let mut lang = lang.clone();
1756            if class_val.is_null() {
1757                lang.attrs.as_mut().map(|a| a.shift_remove("class"));
1758            } else {
1759                lang.set_attr("class".to_string(), class_val);
1760            }
1761            Ok(RValue::Language(lang))
1762        }
1763        other => Ok(other.cloned().unwrap_or(RValue::Null)),
1764    }
1765}
1766
1767/// Set the old-style (S3) class attribute — alias for class<-.
1768/// @param x object
1769/// @param value class names or NULL
1770/// @return modified object
1771/// @namespace base
1772#[builtin(name = "oldClass<-", min_args = 2)]
1773fn builtin_old_class_set(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1774    builtin_class_set(args, named)
1775}
1776
1777/// Get the internal type of an object.
1778///
1779/// Returns the low-level type name (e.g., "double", "integer", "character",
1780/// "logical", "list", "closure", "builtin", "NULL").
1781///
1782/// @param x object to inspect
1783/// @return character scalar
1784#[builtin(min_args = 1)]
1785fn builtin_typeof(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1786    let t = args.first().map(|v| v.type_name()).unwrap_or("NULL");
1787    Ok(RValue::vec(Vector::Character(
1788        vec![Some(t.to_string())].into(),
1789    )))
1790}
1791
1792/// Get the class of an object.
1793///
1794/// Returns the explicit class attribute if set, otherwise the implicit
1795/// class based on the object's type (e.g., "numeric", "character", "list").
1796///
1797/// @param x object to inspect
1798/// @return character vector of class names
1799#[builtin(min_args = 1)]
1800fn builtin_class(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1801    // Check for explicit class attribute on vectors
1802    if let Some(RValue::Vector(rv)) = args.first() {
1803        if let Some(cls) = rv.get_attr("class") {
1804            return Ok(cls.clone());
1805        }
1806    }
1807    // Check for explicit class attribute on lists
1808    if let Some(RValue::List(l)) = args.first() {
1809        if let Some(cls) = l.get_attr("class") {
1810            return Ok(cls.clone());
1811        }
1812    }
1813    if let Some(RValue::Language(lang)) = args.first() {
1814        if let Some(cls) = lang.get_attr("class") {
1815            return Ok(cls.clone());
1816        }
1817    }
1818    let c = match args.first() {
1819        Some(RValue::Vector(rv)) => match &rv.inner {
1820            Vector::Raw(_) => "raw",
1821            Vector::Logical(_) => "logical",
1822            Vector::Integer(_) => "integer",
1823            Vector::Double(_) => "numeric",
1824            Vector::Complex(_) => "complex",
1825            Vector::Character(_) => "character",
1826        },
1827        Some(RValue::List(_)) => "list",
1828        Some(RValue::Function(_)) => "function",
1829        Some(RValue::Language(lang)) => match &**lang {
1830            Expr::Symbol(_) => "name",
1831            _ => "call",
1832        },
1833        Some(RValue::Null) => "NULL",
1834        _ => "NULL",
1835    };
1836    Ok(RValue::vec(Vector::Character(
1837        vec![Some(c.to_string())].into(),
1838    )))
1839}
1840
1841/// Get the mode (storage type) of an object.
1842///
1843/// Similar to `typeof()` but maps integer and double to "numeric".
1844///
1845/// @param x object to inspect
1846/// @return character scalar
1847#[builtin(min_args = 1)]
1848fn builtin_mode(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
1849    let m = match args.first() {
1850        Some(RValue::Vector(rv)) => match &rv.inner {
1851            Vector::Raw(_) => "raw",
1852            Vector::Logical(_) => "logical",
1853            Vector::Integer(_) | Vector::Double(_) => "numeric",
1854            Vector::Complex(_) => "complex",
1855            Vector::Character(_) => "character",
1856        },
1857        Some(RValue::List(_)) => "list",
1858        Some(RValue::Function(_)) => "function",
1859        Some(RValue::Language(lang)) => match &**lang {
1860            Expr::Symbol(_) => "name",
1861            _ => "call",
1862        },
1863        Some(RValue::Null) => "NULL",
1864        _ => "NULL",
1865    };
1866    Ok(RValue::vec(Vector::Character(
1867        vec![Some(m.to_string())].into(),
1868    )))
1869}
1870
1871/// Display the compact internal structure of an object.
1872///
1873/// Prints type, length, and a preview of the first few elements.
1874/// For data.frames, shows a structured view with column types aligned
1875/// using the `tabled` crate.
1876///
1877/// @param x object to inspect
1878/// @return NULL (invisibly)
1879#[interpreter_builtin(min_args = 1)]
1880fn interp_str(
1881    args: &[RValue],
1882    _named: &[(String, RValue)],
1883    context: &BuiltinContext,
1884) -> Result<RValue, RError> {
1885    match args.first() {
1886        Some(val) => {
1887            // Check for data.frame first — use tabled-based structured display
1888            #[cfg(feature = "tables")]
1889            if let Some(output) = tables_display::str_data_frame(val) {
1890                context.write(&output);
1891                return Ok(RValue::Null);
1892            }
1893
1894            match val {
1895                RValue::Vector(v) => {
1896                    let len = v.len();
1897                    let type_name = v.type_name();
1898                    let preview: String = match &v.inner {
1899                        Vector::Raw(vals) => {
1900                            vals.iter().take(10).map(|b| format!("{:02x}", b)).join(" ")
1901                        }
1902                        Vector::Double(vals) => vals
1903                            .iter()
1904                            .take(10)
1905                            .map(|x| match x {
1906                                Some(f) => format_r_double(f),
1907                                None => "NA".to_string(),
1908                            })
1909                            .join(" "),
1910                        Vector::Integer(vals) => vals
1911                            .iter()
1912                            .take(10)
1913                            .map(|x| match x {
1914                                Some(i) => i.to_string(),
1915                                None => "NA".to_string(),
1916                            })
1917                            .join(" "),
1918                        Vector::Logical(vals) => vals
1919                            .iter()
1920                            .take(10)
1921                            .map(|x| match x {
1922                                Some(true) => "TRUE".to_string(),
1923                                Some(false) => "FALSE".to_string(),
1924                                None => "NA".to_string(),
1925                            })
1926                            .join(" "),
1927                        Vector::Complex(vals) => vals
1928                            .iter()
1929                            .take(10)
1930                            .map(|x| match x {
1931                                Some(c) => format_r_complex(*c),
1932                                None => "NA".to_string(),
1933                            })
1934                            .join(" "),
1935                        Vector::Character(vals) => vals
1936                            .iter()
1937                            .take(10)
1938                            .map(|x| match x {
1939                                Some(s) => format!("\"{}\"", s),
1940                                None => "NA".to_string(),
1941                            })
1942                            .join(" "),
1943                    };
1944                    context.write(&format!(" {} [1:{}] {}\n", type_name, len, preview));
1945                }
1946                RValue::List(l) => {
1947                    context.write(&format!("List of {}\n", l.values.len()));
1948                    for (i, (name, elem)) in l.values.iter().enumerate() {
1949                        let label = name.clone().unwrap_or_else(|| format!("[[{}]]", i + 1));
1950                        let child = str_format_element(elem, 1);
1951                        context.write(&format!(" $ {:<13}:{}\n", label, child));
1952                    }
1953                }
1954                RValue::Null => context.write(" NULL\n"),
1955                _ => context.write(&format!(" {}\n", val)),
1956            }
1957            Ok(RValue::Null)
1958        }
1959        None => Ok(RValue::Null),
1960    }
1961}
1962
1963/// Format one element for str() output (used for list elements).
1964/// Returns a one-line summary: " type [1:len] preview" for vectors,
1965/// "List of N" for nested lists, etc.
1966fn str_format_element(val: &RValue, indent: usize) -> String {
1967    let prefix = " ".repeat(indent);
1968    match val {
1969        RValue::Null => format!("{prefix} NULL"),
1970        RValue::Vector(v) => {
1971            let len = v.len();
1972            let type_name = v.type_name();
1973            let preview = str_vector_preview(v);
1974            if len == 1 {
1975                format!("{prefix} {type_name} {preview}")
1976            } else {
1977                let dots = if len > 10 { " ..." } else { "" };
1978                format!("{prefix} {type_name} [1:{len}] {preview}{dots}")
1979            }
1980        }
1981        RValue::List(l) => {
1982            let mut out = format!("{prefix}List of {}", l.values.len());
1983            for (i, (name, elem)) in l.values.iter().enumerate() {
1984                let label = name.clone().unwrap_or_else(|| format!("[[{}]]", i + 1));
1985                let child = str_format_element(elem, indent + 1);
1986                out.push_str(&format!("\n{prefix} $ {label:<13}:{child}"));
1987            }
1988            out
1989        }
1990        RValue::Function(_) => format!("{prefix}function (...)"),
1991        RValue::Environment(_) => format!("{prefix}<environment>"),
1992        RValue::Language(_) => format!("{prefix} language ..."),
1993        RValue::Promise(p) => {
1994            let inner = p.borrow();
1995            if let Some(ref val) = inner.value {
1996                str_format_element(val, indent)
1997            } else {
1998                format!("{prefix} promise ...")
1999            }
2000        }
2001    }
2002}
2003
2004/// Format a vector element preview for str() output (first 10 elements).
2005fn str_vector_preview(v: &RVector) -> String {
2006    let len = v.inner.len().min(10);
2007    let elems: Vec<String> = (0..len)
2008        .map(|i| match &v.inner {
2009            Vector::Raw(vals) => format!("{:02x}", vals[i]),
2010            Vector::Logical(vals) => match vals[i] {
2011                Some(true) => "TRUE".to_string(),
2012                Some(false) => "FALSE".to_string(),
2013                None => "NA".to_string(),
2014            },
2015            Vector::Integer(vals) => match vals.get_opt(i) {
2016                Some(n) => n.to_string(),
2017                None => "NA".to_string(),
2018            },
2019            Vector::Double(vals) => match vals.get_opt(i) {
2020                Some(f) => format_r_double(f),
2021                None => "NA".to_string(),
2022            },
2023            Vector::Complex(vals) => match vals[i] {
2024                Some(c) => format_r_complex(c),
2025                None => "NA".to_string(),
2026            },
2027            Vector::Character(vals) => match &vals[i] {
2028                Some(s) => format!("\"{}\"", s),
2029                None => "NA".to_string(),
2030            },
2031        })
2032        .collect();
2033    elems.join(" ")
2034}
2035
2036/// Test if two objects are exactly identical.
2037///
2038/// Performs deep structural comparison: type, length, element values,
2039/// and attributes must all match. NaN == NaN is TRUE (unlike `==`).
2040/// NA == NA is TRUE. Lists are compared recursively.
2041///
2042/// @param x first object
2043/// @param y second object
2044/// @return logical scalar
2045#[builtin(min_args = 2)]
2046fn builtin_identical(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
2047    if args.len() < 2 {
2048        return Err(RError::new(
2049            RErrorKind::Argument,
2050            "need 2 arguments".to_string(),
2051        ));
2052    }
2053    let result = r_identical(&args[0], &args[1]);
2054    Ok(RValue::vec(Vector::Logical(vec![Some(result)].into())))
2055}
2056
2057/// Deep structural comparison of two R values.
2058///
2059/// In R, `identical()` treats NaN == NaN as TRUE and NA == NA as TRUE,
2060/// unlike the `==` operator. Attributes must also match.
2061fn r_identical(a: &RValue, b: &RValue) -> bool {
2062    match (a, b) {
2063        (RValue::Null, RValue::Null) => true,
2064        (RValue::Vector(va), RValue::Vector(vb)) => {
2065            vectors_identical(&va.inner, &vb.inner) && attrs_identical(&va.attrs, &vb.attrs)
2066        }
2067        (RValue::List(la), RValue::List(lb)) => {
2068            if la.values.len() != lb.values.len() {
2069                return false;
2070            }
2071            for ((na, va), (nb, vb)) in la.values.iter().zip(lb.values.iter()) {
2072                if na != nb {
2073                    return false;
2074                }
2075                if !r_identical(va, vb) {
2076                    return false;
2077                }
2078            }
2079            attrs_identical(&la.attrs, &lb.attrs)
2080        }
2081        (RValue::Function(fa), RValue::Function(fb)) => match (fa, fb) {
2082            (RFunction::Builtin { name: na, .. }, RFunction::Builtin { name: nb, .. }) => na == nb,
2083            (
2084                RFunction::Closure {
2085                    params: pa,
2086                    body: ba,
2087                    ..
2088                },
2089                RFunction::Closure {
2090                    params: pb,
2091                    body: bb,
2092                    ..
2093                },
2094            ) => pa == pb && ba == bb,
2095            _ => false,
2096        },
2097        (RValue::Environment(ea), RValue::Environment(eb)) => {
2098            // Environments are identical only if they are the same Rc (pointer equality)
2099            ea.ptr_eq(eb)
2100        }
2101        (RValue::Language(la), RValue::Language(lb)) => {
2102            *la.inner == *lb.inner && attrs_identical(&la.attrs, &lb.attrs)
2103        }
2104        _ => false,
2105    }
2106}
2107
2108/// Compare two attribute maps for identical-ness.
2109fn attrs_identical(a: &Option<Box<Attributes>>, b: &Option<Box<Attributes>>) -> bool {
2110    match (a, b) {
2111        (None, None) => true,
2112        (Some(aa), Some(bb)) => {
2113            if aa.len() != bb.len() {
2114                return false;
2115            }
2116            for (key, val_a) in aa.iter() {
2117                match bb.get(key) {
2118                    Some(val_b) => {
2119                        if !r_identical(val_a, val_b) {
2120                            return false;
2121                        }
2122                    }
2123                    None => return false,
2124                }
2125            }
2126            true
2127        }
2128        _ => false,
2129    }
2130}
2131
2132/// Element-wise comparison of two atomic vectors.
2133///
2134/// Both vectors must have the same type and length, and every element must
2135/// be bitwise-identical (NaN == NaN is TRUE, NA == NA is TRUE).
2136fn vectors_identical(a: &Vector, b: &Vector) -> bool {
2137    match (a, b) {
2138        (Vector::Logical(va), Vector::Logical(vb)) => va.0 == vb.0,
2139        (Vector::Integer(va), Vector::Integer(vb)) => va.0 == vb.0,
2140        (Vector::Character(va), Vector::Character(vb)) => va.0 == vb.0,
2141        (Vector::Raw(va), Vector::Raw(vb)) => va == vb,
2142        (Vector::Double(va), Vector::Double(vb)) => {
2143            if va.len() != vb.len() {
2144                return false;
2145            }
2146            va.iter().zip(vb.iter()).all(|(x, y)| match (x, y) {
2147                (None, None) => true,
2148                (Some(fx), Some(fy)) => fx.to_bits() == fy.to_bits(),
2149                _ => false,
2150            })
2151        }
2152        (Vector::Complex(va), Vector::Complex(vb)) => {
2153            if va.len() != vb.len() {
2154                return false;
2155            }
2156            va.iter().zip(vb.iter()).all(|(x, y)| match (x, y) {
2157                (None, None) => true,
2158                (Some(cx), Some(cy)) => {
2159                    cx.re.to_bits() == cy.re.to_bits() && cx.im.to_bits() == cy.im.to_bits()
2160                }
2161                _ => false,
2162            })
2163        }
2164        _ => false, // different types are never identical
2165    }
2166}
2167
2168/// Test near-equality of two objects within a tolerance.
2169///
2170/// Returns TRUE if the objects are nearly equal, or a character vector of
2171/// strings describing the differences. Matches R's `all.equal()` semantics:
2172/// numeric comparison uses mean relative/absolute difference with a
2173/// configurable tolerance.
2174///
2175/// @param target first object
2176/// @param current second object
2177/// @param tolerance maximum allowed difference (default: 1.5e-8)
2178/// @param check.attributes if TRUE, also compare attributes
2179/// @param check.names if TRUE, compare names attributes
2180/// @return TRUE if equal, or character string(s) describing the difference(s)
2181#[builtin(min_args = 2)]
2182fn builtin_all_equal(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2183    let tolerance = named
2184        .iter()
2185        .find(|(n, _)| n == "tolerance")
2186        .and_then(|(_, v)| v.as_vector()?.as_double_scalar())
2187        .unwrap_or(1.5e-8);
2188
2189    let check_attributes = named
2190        .iter()
2191        .find(|(n, _)| n == "check.attributes")
2192        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
2193        .unwrap_or(true);
2194
2195    let check_names = named
2196        .iter()
2197        .find(|(n, _)| n == "check.names")
2198        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
2199        .unwrap_or(true);
2200
2201    if args.len() < 2 {
2202        return Err(RError::new(
2203            RErrorKind::Argument,
2204            "need 2 arguments".to_string(),
2205        ));
2206    }
2207
2208    let mut diffs = Vec::new();
2209    all_equal_recurse(
2210        &args[0],
2211        &args[1],
2212        tolerance,
2213        check_attributes,
2214        check_names,
2215        "",
2216        &mut diffs,
2217    );
2218
2219    if diffs.is_empty() {
2220        Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
2221    } else {
2222        let msgs: Vec<Option<String>> = diffs.into_iter().map(Some).collect();
2223        Ok(RValue::vec(Vector::Character(msgs.into())))
2224    }
2225}
2226
2227/// Recursively compare two R values, collecting difference messages.
2228fn all_equal_recurse(
2229    target: &RValue,
2230    current: &RValue,
2231    tolerance: f64,
2232    check_attributes: bool,
2233    check_names: bool,
2234    prefix: &str,
2235    diffs: &mut Vec<String>,
2236) {
2237    match (target, current) {
2238        // NULL == NULL
2239        (RValue::Null, RValue::Null) => {}
2240
2241        // Type mismatch involving NULL
2242        (RValue::Null, _) | (_, RValue::Null) => {
2243            diffs.push(format!(
2244                "{}target is {}, current is {}",
2245                prefix,
2246                target.type_name(),
2247                current.type_name()
2248            ));
2249        }
2250
2251        // Both vectors
2252        (RValue::Vector(v1), RValue::Vector(v2)) => {
2253            all_equal_vectors(
2254                v1,
2255                v2,
2256                tolerance,
2257                check_attributes,
2258                check_names,
2259                prefix,
2260                diffs,
2261            );
2262        }
2263
2264        // Both lists
2265        (RValue::List(l1), RValue::List(l2)) => {
2266            all_equal_lists(
2267                l1,
2268                l2,
2269                tolerance,
2270                check_attributes,
2271                check_names,
2272                prefix,
2273                diffs,
2274            );
2275        }
2276
2277        // Mismatched types
2278        _ => {
2279            diffs.push(format!(
2280                "{}target is {}, current is {}",
2281                prefix,
2282                target.type_name(),
2283                current.type_name()
2284            ));
2285        }
2286    }
2287}
2288
2289/// Compare two vectors, collecting difference messages.
2290fn all_equal_vectors(
2291    v1: &RVector,
2292    v2: &RVector,
2293    tolerance: f64,
2294    check_attributes: bool,
2295    check_names: bool,
2296    prefix: &str,
2297    diffs: &mut Vec<String>,
2298) {
2299    let t1 = v1.inner.type_name();
2300    let t2 = v2.inner.type_name();
2301
2302    // Numeric types (double, integer, logical) can be compared numerically
2303    let is_numeric = |t: &str| matches!(t, "double" | "integer" | "logical");
2304
2305    if t1 == "character" && t2 == "character" {
2306        all_equal_character(v1, v2, prefix, diffs);
2307    } else if is_numeric(t1) && is_numeric(t2) {
2308        all_equal_numeric(v1, v2, tolerance, prefix, diffs);
2309    } else if t1 != t2 {
2310        diffs.push(format!("{}target is {}, current is {}", prefix, t1, t2));
2311        return;
2312    } else {
2313        // Same non-numeric, non-character type (complex, raw) — coerce to doubles
2314        all_equal_numeric(v1, v2, tolerance, prefix, diffs);
2315    }
2316
2317    // Check attributes if requested
2318    if check_attributes {
2319        all_equal_attrs(
2320            v1.attrs.as_deref(),
2321            v2.attrs.as_deref(),
2322            check_names,
2323            prefix,
2324            diffs,
2325        );
2326    } else if check_names {
2327        // Even with check.attributes=FALSE, check.names=TRUE checks names
2328        all_equal_names_attr(v1.attrs.as_deref(), v2.attrs.as_deref(), prefix, diffs);
2329    }
2330}
2331
2332/// Compare numeric vectors using R's mean relative/absolute difference.
2333fn all_equal_numeric(
2334    v1: &RVector,
2335    v2: &RVector,
2336    tolerance: f64,
2337    prefix: &str,
2338    diffs: &mut Vec<String>,
2339) {
2340    let d1 = v1.to_doubles();
2341    let d2 = v2.to_doubles();
2342
2343    if d1.len() != d2.len() {
2344        diffs.push(format!(
2345            "{}Lengths ({}, {}) differ",
2346            prefix,
2347            d1.len(),
2348            d2.len()
2349        ));
2350        return;
2351    }
2352
2353    if d1.is_empty() {
2354        return;
2355    }
2356
2357    // Compute mean absolute difference and mean absolute target (R's algorithm)
2358    let mut sum_abs_diff = 0.0;
2359    let mut sum_abs_target = 0.0;
2360    let mut count = 0usize;
2361
2362    for (a, b) in d1.iter().zip(d2.iter()) {
2363        match (a, b) {
2364            (Some(a), Some(b)) => {
2365                sum_abs_diff += (a - b).abs();
2366                sum_abs_target += a.abs();
2367                count += 1;
2368            }
2369            (None, None) => {} // both NA — equal
2370            _ => {
2371                // One is NA, the other is not
2372                count += 1;
2373                sum_abs_diff += f64::INFINITY;
2374            }
2375        }
2376    }
2377
2378    if count == 0 {
2379        return;
2380    }
2381
2382    let mean_abs_diff = sum_abs_diff / count as f64;
2383    let mean_abs_target = sum_abs_target / count as f64;
2384
2385    // R uses relative difference when mean(abs(target)) > tolerance,
2386    // otherwise absolute difference
2387    if mean_abs_target.is_finite() && mean_abs_target > tolerance {
2388        let relative_diff = mean_abs_diff / mean_abs_target;
2389        if relative_diff > tolerance {
2390            diffs.push(format!(
2391                "{}Mean relative difference: {}",
2392                prefix, relative_diff
2393            ));
2394        }
2395    } else if mean_abs_diff > tolerance {
2396        diffs.push(format!(
2397            "{}Mean absolute difference: {}",
2398            prefix, mean_abs_diff
2399        ));
2400    }
2401}
2402
2403/// Compare character vectors element-wise.
2404fn all_equal_character(v1: &RVector, v2: &RVector, prefix: &str, diffs: &mut Vec<String>) {
2405    let c1 = v1.to_characters();
2406    let c2 = v2.to_characters();
2407
2408    if c1.len() != c2.len() {
2409        diffs.push(format!(
2410            "{}Lengths ({}, {}) differ",
2411            prefix,
2412            c1.len(),
2413            c2.len()
2414        ));
2415        return;
2416    }
2417
2418    let mut mismatches = 0usize;
2419    for (a, b) in c1.iter().zip(c2.iter()) {
2420        if a != b {
2421            mismatches += 1;
2422        }
2423    }
2424
2425    if mismatches > 0 {
2426        diffs.push(format!(
2427            "{}{} string mismatch{}",
2428            prefix,
2429            mismatches,
2430            if mismatches == 1 { "" } else { "es" }
2431        ));
2432    }
2433}
2434
2435/// Compare two lists recursively.
2436fn all_equal_lists(
2437    l1: &RList,
2438    l2: &RList,
2439    tolerance: f64,
2440    check_attributes: bool,
2441    check_names: bool,
2442    prefix: &str,
2443    diffs: &mut Vec<String>,
2444) {
2445    if l1.values.len() != l2.values.len() {
2446        diffs.push(format!(
2447            "{}Lengths ({}, {}) differ",
2448            prefix,
2449            l1.values.len(),
2450            l2.values.len()
2451        ));
2452        return;
2453    }
2454
2455    // Check names if requested
2456    if check_names {
2457        let names1: Vec<Option<&str>> = l1.values.iter().map(|(n, _)| n.as_deref()).collect();
2458        let names2: Vec<Option<&str>> = l2.values.iter().map(|(n, _)| n.as_deref()).collect();
2459        if names1 != names2 {
2460            diffs.push(format!("{}Component names differ", prefix));
2461        }
2462    }
2463
2464    // Recursively compare elements
2465    for (i, ((_, v1), (name, v2))) in l1.values.iter().zip(l2.values.iter()).enumerate() {
2466        let elem_prefix = match name {
2467            Some(n) => format!("Component \"{}\": ", n),
2468            None => format!("Component {}: ", i + 1),
2469        };
2470        all_equal_recurse(
2471            v1,
2472            v2,
2473            tolerance,
2474            check_attributes,
2475            check_names,
2476            &elem_prefix,
2477            diffs,
2478        );
2479    }
2480
2481    // Check list-level attributes
2482    if check_attributes {
2483        all_equal_attrs(
2484            l1.attrs.as_deref(),
2485            l2.attrs.as_deref(),
2486            check_names,
2487            prefix,
2488            diffs,
2489        );
2490    }
2491}
2492
2493/// Compare attributes of two objects.
2494fn all_equal_attrs(
2495    attrs1: Option<&Attributes>,
2496    attrs2: Option<&Attributes>,
2497    check_names: bool,
2498    prefix: &str,
2499    diffs: &mut Vec<String>,
2500) {
2501    let empty = indexmap::IndexMap::new();
2502    let a1 = attrs1.unwrap_or(&empty);
2503    let a2 = attrs2.unwrap_or(&empty);
2504
2505    let mut keys1: Vec<&str> = a1.keys().map(|s| s.as_str()).collect();
2506    let mut keys2: Vec<&str> = a2.keys().map(|s| s.as_str()).collect();
2507
2508    if !check_names {
2509        keys1.retain(|k| *k != "names");
2510        keys2.retain(|k| *k != "names");
2511    }
2512
2513    keys1.sort();
2514    keys2.sort();
2515
2516    if keys1 != keys2 {
2517        diffs.push(format!("{}Attributes differ", prefix));
2518        return;
2519    }
2520
2521    for key in &keys1 {
2522        if let (Some(v1), Some(v2)) = (a1.get(*key), a2.get(*key)) {
2523            let attr_prefix = format!("{}Attributes: <{}> - ", prefix, key);
2524            all_equal_recurse(v1, v2, 1.5e-8, true, true, &attr_prefix, diffs);
2525        }
2526    }
2527}
2528
2529/// Check only the "names" attribute.
2530fn all_equal_names_attr(
2531    attrs1: Option<&Attributes>,
2532    attrs2: Option<&Attributes>,
2533    prefix: &str,
2534    diffs: &mut Vec<String>,
2535) {
2536    let n1 = attrs1.and_then(|a| a.get("names"));
2537    let n2 = attrs2.and_then(|a| a.get("names"));
2538
2539    match (n1, n2) {
2540        (None, None) => {}
2541        (Some(_), None) | (None, Some(_)) => {
2542            diffs.push(format!("{}Names differ", prefix));
2543        }
2544        (Some(v1), Some(v2)) => {
2545            let attr_prefix = format!("{}Names: ", prefix);
2546            all_equal_recurse(v1, v2, 1.5e-8, false, false, &attr_prefix, diffs);
2547        }
2548    }
2549}
2550
2551/// Test if any values are TRUE.
2552///
2553/// @param ... logical vectors to test
2554/// @param na.rm if TRUE, remove NA values before testing
2555/// @return logical scalar
2556#[builtin]
2557fn builtin_any(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2558    let na_rm = named
2559        .iter()
2560        .find(|(n, _)| n == "na.rm")
2561        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
2562        .unwrap_or(false);
2563
2564    for arg in args {
2565        if let Some(v) = arg.as_vector() {
2566            for l in v.to_logicals() {
2567                match l {
2568                    Some(true) => return Ok(RValue::vec(Vector::Logical(vec![Some(true)].into()))),
2569                    None if !na_rm => return Ok(RValue::vec(Vector::Logical(vec![None].into()))),
2570                    _ => {}
2571                }
2572            }
2573        }
2574    }
2575    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
2576}
2577
2578/// Test if all values are TRUE.
2579///
2580/// @param ... logical vectors to test
2581/// @param na.rm if TRUE, remove NA values before testing
2582/// @return logical scalar
2583#[builtin]
2584fn builtin_all(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2585    let na_rm = named
2586        .iter()
2587        .find(|(n, _)| n == "na.rm")
2588        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
2589        .unwrap_or(false);
2590
2591    for arg in args {
2592        if let Some(v) = arg.as_vector() {
2593            for l in v.to_logicals() {
2594                match l {
2595                    Some(false) => {
2596                        return Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
2597                    }
2598                    None if !na_rm => return Ok(RValue::vec(Vector::Logical(vec![None].into()))),
2599                    _ => {}
2600                }
2601            }
2602        }
2603    }
2604    Ok(RValue::vec(Vector::Logical(vec![Some(true)].into())))
2605}
2606
2607/// Vectorized exclusive OR.
2608///
2609/// Computes element-wise XOR of two logical vectors, recycling the shorter.
2610/// Returns NA where either input is NA.
2611///
2612/// @param x first logical vector
2613/// @param y second logical vector
2614/// @return logical vector
2615#[builtin(min_args = 2)]
2616fn builtin_xor(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
2617    if args.len() < 2 {
2618        return Err(RError::new(
2619            RErrorKind::Argument,
2620            "need 2 arguments".to_string(),
2621        ));
2622    }
2623    let a_vec = match &args[0] {
2624        RValue::Vector(v) => v.to_logicals(),
2625        _ => {
2626            return Err(RError::new(
2627                RErrorKind::Argument,
2628                "argument 'x' must be coercible to logical".to_string(),
2629            ))
2630        }
2631    };
2632    let b_vec = match &args[1] {
2633        RValue::Vector(v) => v.to_logicals(),
2634        _ => {
2635            return Err(RError::new(
2636                RErrorKind::Argument,
2637                "argument 'y' must be coercible to logical".to_string(),
2638            ))
2639        }
2640    };
2641    let len = a_vec.len().max(b_vec.len());
2642    let result: Vec<Option<bool>> = (0..len)
2643        .map(|i| {
2644            let a = a_vec[i % a_vec.len()];
2645            let b = b_vec[i % b_vec.len()];
2646            match (a, b) {
2647                (Some(a), Some(b)) => Some(a ^ b),
2648                _ => None,
2649            }
2650        })
2651        .collect();
2652    Ok(RValue::vec(Vector::Logical(result.into())))
2653}
2654
2655/// Construct a list from the given arguments.
2656///
2657/// Named arguments become named elements of the list.
2658///
2659/// @param ... values to include in the list
2660/// @return list
2661#[builtin]
2662fn builtin_list(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2663    let mut values: Vec<(Option<String>, RValue)> = Vec::new();
2664    for arg in args {
2665        values.push((None, arg.clone()));
2666    }
2667    for (name, val) in named {
2668        values.push((Some(name.clone()), val.clone()));
2669    }
2670    Ok(RValue::List(RList::new(values)))
2671}
2672
2673/// Create a data frame from all combinations of the supplied vectors.
2674///
2675/// Each argument is a vector of values; the result is a data frame with one
2676/// row for every combination. The first factor varies fastest, matching R's
2677/// `expand.grid()` semantics.
2678///
2679/// Uses `itertools::multi_cartesian_product` internally (with reversed input
2680/// order so the first argument cycles fastest).
2681///
2682/// @param ... vectors whose Cartesian product forms the rows
2683/// @return data.frame with `prod(lengths)` rows
2684#[builtin(name = "expand.grid")]
2685fn builtin_expand_grid(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2686    // Collect all input vectors with their names
2687    let mut inputs: Vec<(String, Vec<RValue>)> = Vec::new();
2688    let mut unnamed_idx = 0usize;
2689
2690    // Positional args first, then named — preserving call order
2691    // (R interleaves positional and named in the order they appear,
2692    // but the builtin dispatch separates them. We number unnamed ones
2693    // sequentially as Var1, Var2, … and named ones keep their name.)
2694    for arg in args {
2695        unnamed_idx += 1;
2696        let name = format!("Var{unnamed_idx}");
2697        let items = vector_to_items(arg);
2698        if items.is_empty() {
2699            return Ok(empty_expand_grid());
2700        }
2701        inputs.push((name, items));
2702    }
2703    for (name, val) in named {
2704        if name == "stringsAsFactors" || name == "KEEP.OUT.ATTRS" {
2705            continue; // control args — skip
2706        }
2707        let items = vector_to_items(val);
2708        if items.is_empty() {
2709            return Ok(empty_expand_grid());
2710        }
2711        inputs.push((name.clone(), items));
2712    }
2713
2714    if inputs.is_empty() {
2715        return Ok(empty_expand_grid());
2716    }
2717
2718    // Total number of rows = product of all vector lengths
2719    let nrow: usize = inputs.iter().map(|(_, v)| v.len()).product();
2720
2721    // Build columns using modular arithmetic.
2722    // The first input varies fastest: column k repeats each element
2723    // `repeat_each` times and the whole sequence `repeat_whole` times.
2724    //
2725    //   repeat_each  = product of lengths of inputs 0..(k-1)
2726    //   repeat_whole = nrow / (len_k * repeat_each)
2727    let mut columns: Vec<(Option<String>, RValue)> = Vec::with_capacity(inputs.len());
2728    let mut repeat_each: usize = 1;
2729
2730    for (col_name, items) in &inputs {
2731        let len_k = items.len();
2732        let mut col_values: Vec<RValue> = Vec::with_capacity(nrow);
2733
2734        // Pattern: repeat the whole sequence (nrow / (len_k * repeat_each)) times,
2735        // and within each cycle, repeat each element `repeat_each` times.
2736        let cycle_len = len_k * repeat_each;
2737        let n_cycles = nrow / cycle_len;
2738
2739        for _ in 0..n_cycles {
2740            for item in items {
2741                for _ in 0..repeat_each {
2742                    col_values.push(item.clone());
2743                }
2744            }
2745        }
2746
2747        // Combine the column items into a typed vector
2748        let col_vec = combine_expand_grid_column(&col_values);
2749        columns.push((Some(col_name.clone()), col_vec));
2750
2751        repeat_each *= len_k;
2752    }
2753
2754    // Build the data frame
2755    let col_names: Vec<Option<String>> = columns.iter().map(|(n, _)| n.clone()).collect();
2756    let mut result = RList::new(columns);
2757    result.set_attr(
2758        "class".to_string(),
2759        RValue::vec(Vector::Character(
2760            vec![Some("data.frame".to_string())].into(),
2761        )),
2762    );
2763    result.set_attr(
2764        "names".to_string(),
2765        RValue::vec(Vector::Character(col_names.into())),
2766    );
2767    let row_names: Vec<Option<i64>> = (1..=i64::try_from(nrow)?).map(Some).collect();
2768    result.set_attr(
2769        "row.names".to_string(),
2770        RValue::vec(Vector::Integer(row_names.into())),
2771    );
2772
2773    Ok(RValue::List(result))
2774}
2775
2776/// Return an empty data.frame for expand.grid with zero-length inputs.
2777fn empty_expand_grid() -> RValue {
2778    let mut result = RList::new(Vec::new());
2779    result.set_attr(
2780        "class".to_string(),
2781        RValue::vec(Vector::Character(
2782            vec![Some("data.frame".to_string())].into(),
2783        )),
2784    );
2785    result.set_attr(
2786        "names".to_string(),
2787        RValue::vec(Vector::Character(Vec::<Option<String>>::new().into())),
2788    );
2789    result.set_attr(
2790        "row.names".to_string(),
2791        RValue::vec(Vector::Integer(Vec::<Option<i64>>::new().into())),
2792    );
2793    RValue::List(result)
2794}
2795
2796/// Extract scalar items from an RValue for expand.grid column construction.
2797fn vector_to_items(x: &RValue) -> Vec<RValue> {
2798    match x {
2799        RValue::Vector(rv) => match &rv.inner {
2800            Vector::Double(vals) => vals
2801                .iter_opt()
2802                .map(|v| RValue::vec(Vector::Double(vec![v].into())))
2803                .collect(),
2804            Vector::Integer(vals) => vals
2805                .iter_opt()
2806                .map(|v| RValue::vec(Vector::Integer(vec![v].into())))
2807                .collect(),
2808            Vector::Logical(vals) => vals
2809                .iter()
2810                .map(|v| RValue::vec(Vector::Logical(vec![*v].into())))
2811                .collect(),
2812            Vector::Character(vals) => vals
2813                .iter()
2814                .map(|v| RValue::vec(Vector::Character(vec![v.clone()].into())))
2815                .collect(),
2816            Vector::Complex(vals) => vals
2817                .iter()
2818                .map(|v| RValue::vec(Vector::Complex(vec![*v].into())))
2819                .collect(),
2820            Vector::Raw(vals) => vals
2821                .iter()
2822                .map(|v| RValue::vec(Vector::Raw(vec![*v])))
2823                .collect(),
2824        },
2825        RValue::List(l) => l.values.iter().map(|(_, v)| v.clone()).collect(),
2826        RValue::Null => Vec::new(),
2827        other => vec![other.clone()],
2828    }
2829}
2830
2831/// Combine a column of scalar RValues back into a single typed vector.
2832fn combine_expand_grid_column(items: &[RValue]) -> RValue {
2833    if items.is_empty() {
2834        return RValue::Null;
2835    }
2836
2837    // Determine the type from the first element
2838    let first_type = items[0].type_name();
2839    let all_same = items.iter().all(|i| i.type_name() == first_type);
2840
2841    if all_same {
2842        match first_type {
2843            "double" => {
2844                let vals: Vec<Option<f64>> = items
2845                    .iter()
2846                    .map(|i| {
2847                        i.as_vector()
2848                            .and_then(|v| v.to_doubles().into_iter().next())
2849                            .flatten()
2850                    })
2851                    .collect();
2852                RValue::vec(Vector::Double(vals.into()))
2853            }
2854            "integer" => {
2855                let vals: Vec<Option<i64>> = items
2856                    .iter()
2857                    .map(|i| {
2858                        i.as_vector()
2859                            .and_then(|v| v.to_integers().into_iter().next())
2860                            .flatten()
2861                    })
2862                    .collect();
2863                RValue::vec(Vector::Integer(vals.into()))
2864            }
2865            "logical" => {
2866                let vals: Vec<Option<bool>> = items
2867                    .iter()
2868                    .map(|i| {
2869                        i.as_vector()
2870                            .and_then(|v| v.to_logicals().into_iter().next())
2871                            .flatten()
2872                    })
2873                    .collect();
2874                RValue::vec(Vector::Logical(vals.into()))
2875            }
2876            "character" => {
2877                let vals: Vec<Option<String>> = items
2878                    .iter()
2879                    .map(|i| {
2880                        i.as_vector()
2881                            .and_then(|v| v.to_characters().into_iter().next())
2882                            .flatten()
2883                    })
2884                    .collect();
2885                RValue::vec(Vector::Character(vals.into()))
2886            }
2887            _ => {
2888                // Fall back to list
2889                let entries: Vec<(Option<String>, RValue)> =
2890                    items.iter().map(|v| (None, v.clone())).collect();
2891                RValue::List(RList::new(entries))
2892            }
2893        }
2894    } else {
2895        // Mixed types — coerce to character
2896        let vals: Vec<Option<String>> = items
2897            .iter()
2898            .map(|i| {
2899                i.as_vector()
2900                    .and_then(|v| v.to_characters().into_iter().next())
2901                    .flatten()
2902            })
2903            .collect();
2904        RValue::vec(Vector::Character(vals.into()))
2905    }
2906}
2907
2908/// Create a vector of a given mode and length.
2909///
2910/// @param mode type of vector ("numeric", "integer", "character", "logical", "list")
2911/// @param length number of elements (default: 0)
2912/// @return vector initialized with default values for the mode
2913#[builtin(min_args = 1)]
2914fn builtin_vector(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
2915    let mode = args
2916        .first()
2917        .and_then(|v| v.as_vector()?.as_character_scalar())
2918        .unwrap_or_else(|| "logical".to_string());
2919    let length = args
2920        .get(1)
2921        .and_then(|v| v.as_vector()?.as_integer_scalar())
2922        .unwrap_or(0);
2923    let length = usize::try_from(length).unwrap_or(0);
2924    match mode.as_str() {
2925        "numeric" | "double" => Ok(RValue::vec(Vector::Double(vec![Some(0.0); length].into()))),
2926        "integer" => Ok(RValue::vec(Vector::Integer(vec![Some(0); length].into()))),
2927        "character" => Ok(RValue::vec(Vector::Character(
2928            vec![Some(String::new()); length].into(),
2929        ))),
2930        "logical" => Ok(RValue::vec(Vector::Logical(
2931            vec![Some(false); length].into(),
2932        ))),
2933        "list" => Ok(RValue::List(RList::new(vec![(None, RValue::Null); length]))),
2934        "raw" => Ok(RValue::vec(Vector::Raw(vec![0u8; length]))),
2935        "complex" => Ok(RValue::vec(Vector::Complex(
2936            vec![Some(num_complex::Complex64::new(0.0, 0.0)); length].into(),
2937        ))),
2938        _ => Ok(RValue::vec(Vector::Logical(
2939            vec![Some(false); length].into(),
2940        ))),
2941    }
2942}
2943
2944/// Flatten a list into an atomic vector.
2945///
2946/// When `recursive = TRUE` (default), recursively flattens all nested lists
2947/// into a single atomic vector using the same coercion rules as `c()`.
2948/// When `recursive = FALSE`, flattens only one level of list nesting.
2949///
2950/// @param x list to flatten
2951/// @param recursive whether to flatten recursively (default: TRUE)
2952/// @return atomic vector
2953#[builtin(min_args = 1)]
2954fn builtin_unlist(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
2955    let recursive = named
2956        .iter()
2957        .find(|(k, _)| k == "recursive")
2958        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
2959        .unwrap_or(true);
2960
2961    match args.first() {
2962        Some(RValue::List(l)) => {
2963            let mut all_vals = Vec::new();
2964            if recursive {
2965                collect_list_elements_recursive(l, &mut all_vals);
2966            } else {
2967                // Flatten one level only: extract elements from sub-lists
2968                // but don't recurse deeper
2969                for (_, v) in &l.values {
2970                    match v {
2971                        RValue::List(inner) => {
2972                            for (_, elem) in &inner.values {
2973                                all_vals.push(elem.clone());
2974                            }
2975                        }
2976                        other => all_vals.push(other.clone()),
2977                    }
2978                }
2979            }
2980            // Filter out NULL elements — in R, unlist(list(NULL)) is NULL
2981            all_vals.retain(|v| !matches!(v, RValue::Null));
2982            if all_vals.is_empty() {
2983                return Ok(RValue::Null);
2984            }
2985            builtin_c(&all_vals, &[])
2986        }
2987        Some(other) => Ok(other.clone()),
2988        None => Ok(RValue::Null),
2989    }
2990}
2991
2992/// Recursively collect all non-list elements from a list and its nested sublists.
2993fn collect_list_elements_recursive(list: &RList, out: &mut Vec<RValue>) {
2994    for (_, v) in &list.values {
2995        match v {
2996            RValue::List(inner) => collect_list_elements_recursive(inner, out),
2997            other => out.push(other.clone()),
2998        }
2999    }
3000}
3001
3002// invisible() is implemented as an interpreter_builtin in interp.rs
3003// so it can set the interpreter's visibility flag.
3004
3005/// Return its argument unchanged. Forces evaluation of a promise.
3006/// @param x any R value
3007/// @return x, unchanged
3008#[builtin(name = "force", min_args = 1)]
3009fn builtin_force(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3010    Ok(args[0].clone())
3011}
3012
3013/// Return its argument unchanged (identity function).
3014/// @param x any R value
3015/// @return x, unchanged
3016#[builtin(name = "identity", min_args = 1)]
3017fn builtin_identity(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3018    Ok(args[0].clone())
3019}
3020
3021/// `anyDuplicated(x)` — index of first duplicate, or 0 if none.
3022///
3023/// @param x a vector
3024/// @return integer scalar (1-based index of first dup, or 0)
3025/// @namespace base
3026#[builtin(name = "anyDuplicated", min_args = 1)]
3027fn builtin_any_duplicated(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3028    let chars = match &args[0] {
3029        RValue::Vector(rv) => rv.inner.to_characters(),
3030        RValue::List(list) => list
3031            .values
3032            .iter()
3033            .map(|(_, v)| match v {
3034                RValue::Vector(rv) => rv.inner.as_character_scalar(),
3035                RValue::Null => Some("NULL".to_string()),
3036                _ => Some(format!("{:?}", v)),
3037            })
3038            .collect(),
3039        _ => vec![],
3040    };
3041    let mut seen = std::collections::HashSet::new();
3042    for (i, s) in chars.iter().enumerate() {
3043        let key = s.as_deref().unwrap_or("NA");
3044        if !seen.insert(key.to_string()) {
3045            return Ok(RValue::vec(Vector::Integer(
3046                vec![Some(i64::try_from(i + 1).unwrap_or(0))].into(),
3047            )));
3048        }
3049    }
3050    Ok(RValue::vec(Vector::Integer(vec![Some(0)].into())))
3051}
3052
3053/// `as.function(x, envir)` — convert a list to a function.
3054///
3055/// The list should contain named formals and a body as the last element.
3056/// Example: `as.function(list(x=, y=1, quote(x+y)))` creates `function(x, y=1) x+y`.
3057///
3058/// @param x a list (formals + body)
3059/// @param envir the environment for the new function
3060/// @return a function (closure)
3061/// @namespace base
3062#[builtin(name = "as.function", min_args = 1)]
3063fn builtin_as_function(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3064    as_function_impl(args)
3065}
3066
3067/// `as.function.default` — same as `as.function`, dispatched for default method.
3068#[builtin(name = "as.function.default", min_args = 1)]
3069fn builtin_as_function_default(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3070    as_function_impl(args)
3071}
3072
3073fn as_function_impl(args: &[RValue]) -> Result<RValue, RError> {
3074    let list = match &args[0] {
3075        RValue::List(l) => l,
3076        RValue::Null => {
3077            // as.function(NULL) → function() NULL
3078            return Ok(RValue::Function(RFunction::Closure {
3079                params: vec![],
3080                body: Expr::Null,
3081                env: crate::interpreter::environment::Environment::new_empty(),
3082            }));
3083        }
3084        _ => {
3085            return Err(RError::new(
3086                RErrorKind::Type,
3087                "cannot coerce to function — argument must be a list",
3088            ))
3089        }
3090    };
3091
3092    if list.values.is_empty() {
3093        return Ok(RValue::Function(RFunction::Closure {
3094            params: vec![],
3095            body: Expr::Null,
3096            env: crate::interpreter::environment::Environment::new_empty(),
3097        }));
3098    }
3099
3100    // Last element is the body, preceding named elements are formals
3101    let n = list.values.len();
3102    let mut params = Vec::new();
3103    for (name, val) in &list.values[..n - 1] {
3104        let param_name = name.clone().unwrap_or_default();
3105        if param_name == "..." {
3106            params.push(Param {
3107                name: "...".to_string(),
3108                default: None,
3109                is_dots: true,
3110            });
3111        } else {
3112            // Convert the default value to an Expr
3113            let default = match val {
3114                RValue::Language(lang) => Some((*lang.inner).clone()),
3115                RValue::Vector(_) => Some(crate::interpreter::value::rvalue_to_expr(val)),
3116                RValue::Null => None, // missing default
3117                _ => None,
3118            };
3119            params.push(Param {
3120                name: param_name,
3121                default,
3122                is_dots: false,
3123            });
3124        }
3125    }
3126
3127    // Body is the last element
3128    let body_val = &list.values[n - 1].1;
3129    let body = match body_val {
3130        RValue::Language(lang) => (*lang.inner).clone(),
3131        _ => crate::interpreter::value::rvalue_to_expr(body_val),
3132    };
3133
3134    // Environment defaults to caller's env, but we use an empty env as fallback
3135    // (the actual envir= argument is handled by the interpreter-level caller)
3136    let env = if args.len() > 1 {
3137        match &args[1] {
3138            RValue::Environment(e) => e.clone(),
3139            _ => crate::interpreter::environment::Environment::new_empty(),
3140        }
3141    } else {
3142        crate::interpreter::environment::Environment::new_empty()
3143    };
3144
3145    Ok(RValue::Function(RFunction::Closure { params, body, env }))
3146}
3147
3148/// `I(x)` — mark an object as "AsIs", inhibiting conversion.
3149///
3150/// Adds class "AsIs" to the object. Used in formulas and data.frame()
3151/// to prevent automatic type coercion.
3152///
3153/// @param x any R object
3154/// @return x with class "AsIs" prepended
3155/// @namespace base
3156#[builtin(name = "I", min_args = 1)]
3157fn builtin_asis(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3158    let mut val = args[0].clone();
3159    // Get existing class from the value
3160    let existing: Vec<String> = match &val {
3161        RValue::Vector(rv) => rv.class().unwrap_or_default(),
3162        RValue::List(l) => l.class().unwrap_or_default(),
3163        _ => vec![],
3164    };
3165    let mut new_class: Vec<Option<String>> = vec![Some("AsIs".to_string())];
3166    new_class.extend(existing.into_iter().map(Some));
3167    let class_val = RValue::vec(Vector::Character(new_class.into()));
3168    match &mut val {
3169        RValue::Vector(rv) => rv.set_attr("class".to_string(), class_val),
3170        RValue::List(list) => list.set_attr("class".to_string(), class_val),
3171        RValue::Language(lang) => lang.set_attr("class".to_string(), class_val),
3172        // Functions, environments, promises, and NULL don't carry attributes
3173        // in miniR. Return unchanged — I() on these is a no-op, matching R
3174        // behavior where the class is effectively invisible for these types.
3175        _ => return Ok(val),
3176    }
3177    Ok(val)
3178}
3179
3180/// Create a pairlist from named arguments.
3181///
3182/// In miniR, pairlists are represented as named lists since we don't
3183/// have a separate pairlist type. This is sufficient for most R code.
3184///
3185/// @param ... named arguments
3186/// @return a named list (acting as pairlist)
3187/// @namespace base
3188#[builtin(name = "pairlist")]
3189fn builtin_pairlist(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
3190    let mut entries: Vec<(Option<String>, RValue)> = Vec::new();
3191    // Positional args
3192    for arg in args {
3193        entries.push((None, arg.clone()));
3194    }
3195    // Named args
3196    for (name, val) in named {
3197        entries.push((Some(name.clone()), val.clone()));
3198    }
3199    Ok(RValue::List(RList::new(entries)))
3200}
3201
3202/// Modify a list by replacing/adding/removing elements from another list.
3203///
3204/// For each named element in `val`:
3205/// - If the value is NULL, remove that name from `x`
3206/// - Otherwise, replace or add the element in `x`
3207///
3208/// @param x a list to modify
3209/// @param val a list of replacements
3210/// @return modified copy of x
3211/// @namespace utils
3212#[builtin(name = "modifyList", min_args = 2)]
3213fn builtin_modify_list(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3214    let x = match args.first() {
3215        Some(RValue::List(l)) => l.clone(),
3216        Some(_) => {
3217            return Err(RError::new(
3218                RErrorKind::Type,
3219                "modifyList: first argument must be a list".to_string(),
3220            ))
3221        }
3222        None => return Ok(RValue::Null),
3223    };
3224    let val = match args.get(1) {
3225        Some(RValue::List(l)) => l,
3226        Some(RValue::Null) => return Ok(RValue::List(x)),
3227        _ => return Ok(RValue::List(x)),
3228    };
3229
3230    let mut result = x;
3231    for (name, value) in &val.values {
3232        if let Some(name) = name {
3233            if value.is_null() {
3234                // Remove
3235                result
3236                    .values
3237                    .retain(|(n, _)| n.as_deref() != Some(name.as_str()));
3238            } else if let Some(entry) = result
3239                .values
3240                .iter_mut()
3241                .find(|(n, _)| n.as_deref() == Some(name.as_str()))
3242            {
3243                // Replace
3244                entry.1 = value.clone();
3245            } else {
3246                // Add
3247                result.values.push((Some(name.clone()), value.clone()));
3248            }
3249        }
3250    }
3251    Ok(RValue::List(result))
3252}
3253
3254/// Get the class attribute of an object (S3 compat alias for class()).
3255/// @param x any R value
3256/// @return class attribute or NULL
3257/// @namespace base
3258#[builtin(name = "oldClass", min_args = 1)]
3259fn builtin_old_class(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3260    match args.first() {
3261        Some(RValue::Vector(rv)) => match rv.get_attr("class") {
3262            Some(cls) => Ok(cls.clone()),
3263            None => Ok(RValue::Null),
3264        },
3265        Some(RValue::List(l)) => match l.get_attr("class") {
3266            Some(cls) => Ok(cls.clone()),
3267            None => Ok(RValue::Null),
3268        },
3269        _ => Ok(RValue::Null),
3270    }
3271}
3272
3273/// Vectorized conditional: for each element of test, select the corresponding
3274/// element from yes (when TRUE) or no (when FALSE). yes and no are recycled
3275/// to the length of test.
3276///
3277/// @param test logical vector
3278/// @param yes values to use where test is TRUE (recycled)
3279/// @param no values to use where test is FALSE (recycled)
3280/// @return vector of same length as test, with elements drawn from yes or no
3281#[builtin(min_args = 3)]
3282fn builtin_ifelse(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3283    if args.len() < 3 {
3284        return Err(RError::new(
3285            RErrorKind::Argument,
3286            "need 3 arguments".to_string(),
3287        ));
3288    }
3289    let test_vec = args[0]
3290        .as_vector()
3291        .ok_or_else(|| RError::new(RErrorKind::Argument, "test must be a vector".to_string()))?;
3292    let test_logicals = test_vec.to_logicals();
3293    let n = test_logicals.len();
3294
3295    let yes_vec = args[1]
3296        .as_vector()
3297        .ok_or_else(|| RError::new(RErrorKind::Argument, "yes must be a vector".to_string()))?;
3298    let no_vec = args[2]
3299        .as_vector()
3300        .ok_or_else(|| RError::new(RErrorKind::Argument, "no must be a vector".to_string()))?;
3301
3302    // Determine result type based on R's coercion hierarchy
3303    let is_character =
3304        matches!(yes_vec, Vector::Character(_)) || matches!(no_vec, Vector::Character(_));
3305    let is_logical = matches!(yes_vec, Vector::Logical(_)) && matches!(no_vec, Vector::Logical(_));
3306    let is_integer = !is_character
3307        && (matches!(yes_vec, Vector::Integer(_) | Vector::Logical(_)))
3308        && (matches!(no_vec, Vector::Integer(_) | Vector::Logical(_)));
3309
3310    if is_character {
3311        let yes_chars = yes_vec.to_characters();
3312        let no_chars = no_vec.to_characters();
3313        let result: Vec<Option<String>> = (0..n)
3314            .map(|i| match test_logicals[i] {
3315                Some(true) => yes_chars[i % yes_chars.len()].clone(),
3316                Some(false) => no_chars[i % no_chars.len()].clone(),
3317                None => None,
3318            })
3319            .collect();
3320        Ok(RValue::vec(Vector::Character(result.into())))
3321    } else if is_logical {
3322        let yes_bools = yes_vec.to_logicals();
3323        let no_bools = no_vec.to_logicals();
3324        let result: Vec<Option<bool>> = (0..n)
3325            .map(|i| match test_logicals[i] {
3326                Some(true) => yes_bools[i % yes_bools.len()],
3327                Some(false) => no_bools[i % no_bools.len()],
3328                None => None,
3329            })
3330            .collect();
3331        Ok(RValue::vec(Vector::Logical(result.into())))
3332    } else if is_integer {
3333        let yes_ints = yes_vec.to_integers();
3334        let no_ints = no_vec.to_integers();
3335        let result: Vec<Option<i64>> = (0..n)
3336            .map(|i| match test_logicals[i] {
3337                Some(true) => yes_ints[i % yes_ints.len()],
3338                Some(false) => no_ints[i % no_ints.len()],
3339                None => None,
3340            })
3341            .collect();
3342        Ok(RValue::vec(Vector::Integer(result.into())))
3343    } else {
3344        let yes_doubles = yes_vec.to_doubles();
3345        let no_doubles = no_vec.to_doubles();
3346        let result: Vec<Option<f64>> = (0..n)
3347            .map(|i| match test_logicals[i] {
3348                Some(true) => yes_doubles[i % yes_doubles.len()],
3349                Some(false) => no_doubles[i % no_doubles.len()],
3350                None => None,
3351            })
3352            .collect();
3353        Ok(RValue::vec(Vector::Double(result.into())))
3354    }
3355}
3356
3357/// Find positions of first matches of x in table.
3358///
3359/// For each element of x, returns the position of its first exact match in table,
3360/// or NA if no match is found. Supports case-insensitive matching via `ignore.case`.
3361///
3362/// @param x values to look up
3363/// @param table values to match against
3364/// @param ignore.case logical; if TRUE, comparison is case-insensitive (Unicode-aware)
3365/// @return integer vector of match positions (1-indexed)
3366#[builtin(min_args = 2)]
3367fn builtin_match(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
3368    if args.len() < 2 {
3369        return Err(RError::new(
3370            RErrorKind::Argument,
3371            "need 2 arguments".to_string(),
3372        ));
3373    }
3374
3375    let ignore_case = named
3376        .iter()
3377        .find(|(k, _)| k == "ignore.case")
3378        .and_then(|(_, v)| match v {
3379            RValue::Vector(rv) => rv.inner.as_logical_scalar(),
3380            _ => None,
3381        })
3382        .unwrap_or(false);
3383
3384    let x = match &args[0] {
3385        RValue::Vector(v) => v.to_characters(),
3386        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3387    };
3388    let table = match &args[1] {
3389        RValue::Vector(v) => v.to_characters(),
3390        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3391    };
3392
3393    let result: Vec<Option<i64>> = if ignore_case {
3394        x.iter()
3395            .map(|xi| {
3396                xi.as_ref().and_then(|xi| {
3397                    let key = unicase::UniCase::new(xi.as_str());
3398                    table
3399                        .iter()
3400                        .position(|t| t.as_deref().map(unicase::UniCase::new) == Some(key))
3401                        .map(|p| i64::try_from(p).map(|v| v + 1).unwrap_or(0))
3402                })
3403            })
3404            .collect()
3405    } else {
3406        x.iter()
3407            .map(|xi| {
3408                xi.as_ref().and_then(|xi| {
3409                    table
3410                        .iter()
3411                        .position(|t| t.as_ref() == Some(xi))
3412                        .map(|p| i64::try_from(p).map(|v| v + 1).unwrap_or(0))
3413                })
3414            })
3415            .collect()
3416    };
3417    Ok(RValue::vec(Vector::Integer(result.into())))
3418}
3419
3420/// Partial string matching — find unique partial matches of x in table.
3421///
3422/// For each element of x, returns the position of its unique partial match in table
3423/// (i.e. the table element that starts with x). Returns NA if no match or if
3424/// multiple table entries match (ambiguous). Exact matches are preferred.
3425///
3426/// @param x values to look up (partial strings)
3427/// @param table values to match against
3428/// @return integer vector of match positions (1-indexed), or NA for no/ambiguous match
3429#[builtin(min_args = 2)]
3430fn builtin_pmatch(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3431    let x = match args.first() {
3432        Some(RValue::Vector(v)) => v.to_characters(),
3433        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3434    };
3435    let table = match args.get(1) {
3436        Some(RValue::Vector(v)) => v.to_characters(),
3437        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3438    };
3439
3440    let result: Vec<Option<i64>> = x
3441        .iter()
3442        .map(|xi| {
3443            xi.as_ref().and_then(|xi| {
3444                // First try exact match
3445                if let Some(pos) = table.iter().position(|t| t.as_deref() == Some(xi.as_str())) {
3446                    return Some(i64::try_from(pos).map(|v| v + 1).unwrap_or(0));
3447                }
3448                // Then try unique partial match (prefix)
3449                let matches: Vec<usize> = table
3450                    .iter()
3451                    .enumerate()
3452                    .filter(|(_, t)| {
3453                        t.as_ref()
3454                            .map(|t| t.starts_with(xi.as_str()))
3455                            .unwrap_or(false)
3456                    })
3457                    .map(|(i, _)| i)
3458                    .collect();
3459                if matches.len() == 1 {
3460                    Some(i64::try_from(matches[0]).map(|v| v + 1).unwrap_or(0))
3461                } else {
3462                    None // no match or ambiguous
3463                }
3464            })
3465        })
3466        .collect();
3467    Ok(RValue::vec(Vector::Integer(result.into())))
3468}
3469
3470/// Character partial matching — like pmatch but returns 0 for ambiguous matches.
3471///
3472/// For each element of x, returns the position of its unique partial match in table.
3473/// Returns NA for no match, and 0 for ambiguous matches (multiple partial matches).
3474///
3475/// @param x values to look up
3476/// @param table values to match against
3477/// @return integer vector of match positions (1-indexed), 0 for ambiguous, NA for no match
3478#[builtin(min_args = 2)]
3479fn builtin_charmatch(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3480    let x = match args.first() {
3481        Some(RValue::Vector(v)) => v.to_characters(),
3482        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3483    };
3484    let table = match args.get(1) {
3485        Some(RValue::Vector(v)) => v.to_characters(),
3486        _ => return Ok(RValue::vec(Vector::Integer(vec![None].into()))),
3487    };
3488
3489    let result: Vec<Option<i64>> = x
3490        .iter()
3491        .map(|xi| {
3492            xi.as_ref().and_then(|xi| {
3493                // First check for exact match
3494                if let Some(pos) = table.iter().position(|t| t.as_deref() == Some(xi.as_str())) {
3495                    return Some(i64::try_from(pos).map(|v| v + 1).unwrap_or(0));
3496                }
3497                // Then try partial match (prefix)
3498                let matches: Vec<usize> = table
3499                    .iter()
3500                    .enumerate()
3501                    .filter(|(_, t)| {
3502                        t.as_ref()
3503                            .map(|t| t.starts_with(xi.as_str()))
3504                            .unwrap_or(false)
3505                    })
3506                    .map(|(i, _)| i)
3507                    .collect();
3508                match matches.len() {
3509                    0 => None,                                                        // no match -> NA
3510                    1 => Some(i64::try_from(matches[0]).map(|v| v + 1).unwrap_or(0)), // unique match
3511                    _ => Some(0), // ambiguous -> 0
3512                }
3513            })
3514        })
3515        .collect();
3516    Ok(RValue::vec(Vector::Integer(result.into())))
3517}
3518
3519/// Replace values in a vector at specified indices.
3520///
3521/// Preserves the type of the input vector. Replacement values are coerced
3522/// to the input type and recycled if shorter than the index vector.
3523///
3524/// @param x vector to modify
3525/// @param list indices at which to replace (1-indexed)
3526/// @param values replacement values (recycled if shorter)
3527/// @return modified vector with same type as x
3528#[builtin(min_args = 3)]
3529fn builtin_replace(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3530    if args.len() < 3 {
3531        return Err(RError::new(
3532            RErrorKind::Argument,
3533            "need 3 arguments".to_string(),
3534        ));
3535    }
3536    match &args[0] {
3537        RValue::Vector(v) => {
3538            let indices = args[1]
3539                .as_vector()
3540                .map(|v| v.to_integers())
3541                .unwrap_or_default();
3542            let vals_vec = args[2]
3543                .as_vector()
3544                .cloned()
3545                .unwrap_or(Vector::Logical(vec![None].into()));
3546
3547            match &v.inner {
3548                Vector::Character(_) => {
3549                    let mut data = v.to_characters();
3550                    let vals = vals_vec.to_characters();
3551                    if vals.is_empty() {
3552                        return Ok(RValue::vec(Vector::Character(data.into())));
3553                    }
3554                    for (i, idx) in indices.iter().enumerate() {
3555                        if let Some(idx) = idx {
3556                            let idx = usize::try_from(*idx)? - 1;
3557                            if idx < data.len() {
3558                                data[idx] = vals[i % vals.len()].clone();
3559                            }
3560                        }
3561                    }
3562                    Ok(RValue::vec(Vector::Character(data.into())))
3563                }
3564                Vector::Integer(_) => {
3565                    let mut data = v.to_integers();
3566                    let vals = vals_vec.to_integers();
3567                    if vals.is_empty() {
3568                        return Ok(RValue::vec(Vector::Integer(data.into())));
3569                    }
3570                    for (i, idx) in indices.iter().enumerate() {
3571                        if let Some(idx) = idx {
3572                            let idx = usize::try_from(*idx)? - 1;
3573                            if idx < data.len() {
3574                                data[idx] = vals[i % vals.len()];
3575                            }
3576                        }
3577                    }
3578                    Ok(RValue::vec(Vector::Integer(data.into())))
3579                }
3580                Vector::Logical(_) => {
3581                    let mut data = v.to_logicals();
3582                    let vals = vals_vec.to_logicals();
3583                    if vals.is_empty() {
3584                        return Ok(RValue::vec(Vector::Logical(data.into())));
3585                    }
3586                    for (i, idx) in indices.iter().enumerate() {
3587                        if let Some(idx) = idx {
3588                            let idx = usize::try_from(*idx)? - 1;
3589                            if idx < data.len() {
3590                                data[idx] = vals[i % vals.len()];
3591                            }
3592                        }
3593                    }
3594                    Ok(RValue::vec(Vector::Logical(data.into())))
3595                }
3596                Vector::Complex(_) => {
3597                    let mut data = v.to_complex();
3598                    let vals = vals_vec.to_complex();
3599                    if vals.is_empty() {
3600                        return Ok(RValue::vec(Vector::Complex(data.into())));
3601                    }
3602                    for (i, idx) in indices.iter().enumerate() {
3603                        if let Some(idx) = idx {
3604                            let idx = usize::try_from(*idx)? - 1;
3605                            if idx < data.len() {
3606                                data[idx] = vals[i % vals.len()];
3607                            }
3608                        }
3609                    }
3610                    Ok(RValue::vec(Vector::Complex(data.into())))
3611                }
3612                _ => {
3613                    // Double and Raw fall through to double replacement
3614                    let mut data = v.to_doubles();
3615                    let vals = vals_vec.to_doubles();
3616                    if vals.is_empty() {
3617                        return Ok(RValue::vec(Vector::Double(data.into())));
3618                    }
3619                    for (i, idx) in indices.iter().enumerate() {
3620                        if let Some(idx) = idx {
3621                            let idx = usize::try_from(*idx)? - 1;
3622                            if idx < data.len() {
3623                                data[idx] = vals[i % vals.len()];
3624                            }
3625                        }
3626                    }
3627                    Ok(RValue::vec(Vector::Double(data.into())))
3628                }
3629            }
3630        }
3631        _ => Ok(args[0].clone()),
3632    }
3633}
3634
3635// options() and getOption() are interpreter builtins in interp.rs
3636
3637// Sys.time() is in datetime.rs; proc.time() is in system.rs
3638
3639/// Read a line of user input from stdin.
3640///
3641/// @param prompt string to display before reading input
3642/// @return character scalar of the input (without trailing newline)
3643#[interpreter_builtin]
3644fn interp_readline(
3645    args: &[RValue],
3646    _named: &[(String, RValue)],
3647    context: &BuiltinContext,
3648) -> Result<RValue, RError> {
3649    let prompt = args
3650        .first()
3651        .and_then(|v| v.as_vector()?.as_character_scalar())
3652        .unwrap_or_default();
3653    context.write(&prompt);
3654    let mut input = String::new();
3655    std::io::stdin().read_line(&mut input).ok();
3656    Ok(RValue::vec(Vector::Character(
3657        vec![Some(input.trim_end().to_string())].into(),
3658    )))
3659}
3660
3661/// Get environment variable(s) from the interpreter's environment.
3662///
3663/// @param x character vector of variable names. If missing, returns all env vars
3664///   as a named character vector.
3665/// @param unset character scalar: value to return for unset variables (default "")
3666/// @param names logical: if TRUE (default), set names on the result
3667/// @return character vector with the variable values
3668#[interpreter_builtin(name = "Sys.getenv")]
3669fn interp_sys_getenv(
3670    args: &[RValue],
3671    named: &[(String, RValue)],
3672    context: &BuiltinContext,
3673) -> Result<RValue, RError> {
3674    // Extract the `unset` parameter (default: "")
3675    // unset = None means NA was passed (return NA for missing vars)
3676    // unset = Some("") means default (return "" for missing vars)
3677    let unset: Option<String> = match named.iter().find(|(n, _)| n == "unset") {
3678        None => Some(String::new()), // not specified → default ""
3679        Some((_, v)) => {
3680            // Check for NA — if the value is a character NA, return None
3681            if let Some(vec) = v.as_vector() {
3682                let chars = vec.to_characters();
3683                if chars.len() == 1 && chars[0].is_none() {
3684                    None // NA unset → return NA for missing vars
3685                } else {
3686                    Some(chars.into_iter().next().flatten().unwrap_or_default())
3687                }
3688            } else {
3689                Some(String::new())
3690            }
3691        }
3692    };
3693
3694    let use_names = named
3695        .iter()
3696        .find(|(n, _)| n == "names")
3697        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
3698        .unwrap_or(true);
3699
3700    // If no args, return all env vars as a named vector
3701    let first_arg = args.first();
3702    if first_arg.is_none() {
3703        let snapshot = context.with_interpreter(|interp| interp.env_vars_snapshot());
3704        let mut pairs: Vec<(String, String)> = snapshot.into_iter().collect();
3705        pairs.sort_by(|a, b| a.0.cmp(&b.0));
3706        let names_vec: Vec<Option<String>> = pairs.iter().map(|(n, _)| Some(n.clone())).collect();
3707        let vals: Vec<Option<String>> = pairs.into_iter().map(|(_, v)| Some(v)).collect();
3708        let mut rv = RVector::from(Vector::Character(vals.into()));
3709        rv.set_attr(
3710            "names".to_string(),
3711            RValue::vec(Vector::Character(names_vec.into())),
3712        );
3713        return Ok(RValue::Vector(rv));
3714    }
3715
3716    // Vectorized: support Sys.getenv(c("HOME", "NOSUCH"))
3717    let var_names: Vec<Option<String>> = first_arg
3718        .and_then(|v| v.as_vector())
3719        .map(|v| v.to_characters())
3720        .unwrap_or_default();
3721
3722    let values: Vec<Option<String>> = var_names
3723        .iter()
3724        .map(|name_opt| {
3725            let name = name_opt.as_deref().unwrap_or("");
3726            let val = context.with_interpreter(|interp| interp.get_env_var(name));
3727            match val {
3728                Some(v) => Some(v),
3729                None => unset.clone(), // None → NA, Some(s) → default value
3730            }
3731        })
3732        .collect();
3733
3734    if use_names && var_names.len() > 1 {
3735        let names_vec: Vec<Option<String>> = var_names.clone();
3736        let mut rv = RVector::from(Vector::Character(values.into()));
3737        rv.set_attr(
3738            "names".to_string(),
3739            RValue::vec(Vector::Character(names_vec.into())),
3740        );
3741        Ok(RValue::Vector(rv))
3742    } else if use_names && var_names.len() == 1 {
3743        let mut rv = RVector::from(Vector::Character(values.into()));
3744        rv.set_attr(
3745            "names".to_string(),
3746            RValue::vec(Vector::Character(var_names.into())),
3747        );
3748        Ok(RValue::Vector(rv))
3749    } else {
3750        Ok(RValue::vec(Vector::Character(values.into())))
3751    }
3752}
3753
3754// (file.path, file.exists, readLines, writeLines, read.csv, write.csv — io.rs)
3755
3756// library() and require() are in pre_eval.rs (NSE for bare package names)
3757
3758/// Load a package namespace without attaching it to the search path.
3759///
3760/// Creates the namespace environment, sources all R files, and registers
3761/// exports, but does not add the package to the search path. This is the
3762/// mechanism underlying `library()` and `::` resolution.
3763///
3764/// @param package character scalar: the package name
3765/// @return the namespace environment
3766#[interpreter_builtin(name = "loadNamespace", min_args = 1)]
3767fn interp_load_namespace(
3768    args: &[RValue],
3769    _named: &[(String, RValue)],
3770    context: &BuiltinContext,
3771) -> Result<RValue, RError> {
3772    let pkg = args
3773        .first()
3774        .and_then(|v| v.as_vector()?.as_character_scalar())
3775        .ok_or_else(|| RError::new(RErrorKind::Argument, "invalid package name".to_string()))?;
3776
3777    let env = context.with_interpreter(|interp| interp.load_namespace(&pkg))?;
3778    Ok(RValue::Environment(env))
3779}
3780
3781/// Check if a namespace can be loaded, returning TRUE/FALSE.
3782///
3783/// Like loadNamespace(), but returns TRUE/FALSE instead of raising an error.
3784/// Useful for conditional logic that depends on package availability.
3785///
3786/// @param package character scalar: the package name
3787/// @param quietly logical: suppress messages? (default TRUE)
3788/// @return logical: TRUE if the namespace could be loaded
3789#[interpreter_builtin(name = "requireNamespace", min_args = 1)]
3790fn interp_require_namespace(
3791    args: &[RValue],
3792    named: &[(String, RValue)],
3793    context: &BuiltinContext,
3794) -> Result<RValue, RError> {
3795    let pkg = args
3796        .first()
3797        .and_then(|v| v.as_vector()?.as_character_scalar())
3798        .ok_or_else(|| RError::new(RErrorKind::Argument, "invalid package name".to_string()))?;
3799
3800    let quietly = named
3801        .iter()
3802        .find(|(n, _)| n == "quietly")
3803        .and_then(|(_, v)| v.as_vector()?.as_logical_scalar())
3804        .unwrap_or(true);
3805
3806    let result = context.with_interpreter(|interp| interp.load_namespace(&pkg));
3807    match result {
3808        Ok(_) => Ok(RValue::vec(Vector::Logical(vec![Some(true)].into()))),
3809        Err(_) => {
3810            if !quietly {
3811                context.write_err(&format!(
3812                    "Warning message:\nthere is no package called '{}'\n",
3813                    pkg
3814                ));
3815            }
3816            Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
3817        }
3818    }
3819}
3820
3821/// Detach a package from the search path.
3822///
3823/// @param name the search path entry to detach (e.g. "package:dplyr")
3824/// @return NULL (invisibly)
3825#[interpreter_builtin(name = "detach", min_args = 1)]
3826fn interp_detach(
3827    args: &[RValue],
3828    named: &[(String, RValue)],
3829    context: &BuiltinContext,
3830) -> Result<RValue, RError> {
3831    let name = named
3832        .iter()
3833        .find(|(n, _)| n == "name")
3834        .map(|(_, v)| v)
3835        .or_else(|| args.first())
3836        .and_then(|v| v.as_vector()?.as_character_scalar())
3837        .ok_or_else(|| RError::new(RErrorKind::Argument, "invalid 'name' argument".to_string()))?;
3838
3839    // If user said just "dplyr", prefix with "package:"
3840    let entry_name = if name.starts_with("package:") {
3841        name
3842    } else {
3843        format!("package:{}", name)
3844    };
3845
3846    context.with_interpreter(|interp| interp.detach_package(&entry_name))?;
3847    Ok(RValue::Null)
3848}
3849
3850/// Get the version of the R implementation.
3851///
3852/// Returns a list with major, minor, language, and engine fields.
3853///
3854/// @return named list of version information
3855#[builtin(name = "R.Version", names = ["version"])]
3856fn builtin_r_version(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3857    Ok(RValue::List(RList::new(vec![
3858        (
3859            Some("major".to_string()),
3860            RValue::vec(Vector::Character(vec![Some("0".to_string())].into())),
3861        ),
3862        (
3863            Some("minor".to_string()),
3864            RValue::vec(Vector::Character(vec![Some("1.0".to_string())].into())),
3865        ),
3866        (
3867            Some("language".to_string()),
3868            RValue::vec(Vector::Character(vec![Some("R".to_string())].into())),
3869        ),
3870        (
3871            Some("engine".to_string()),
3872            RValue::vec(Vector::Character(
3873                vec![Some("miniR (Rust)".to_string())].into(),
3874            )),
3875        ),
3876    ])))
3877}
3878
3879/// Canonical string key for set operations — works across numeric and character types.
3880fn set_key(val: &Option<String>) -> String {
3881    match val {
3882        Some(s) => s.clone(),
3883        None => "NA".to_string(),
3884    }
3885}
3886
3887/// Determine whether set operations should work in numeric or character mode.
3888/// Returns the result type based on input types: numeric inputs stay numeric.
3889fn set_op_extract(
3890    x: &RValue,
3891    y: &RValue,
3892) -> (
3893    Vec<Option<String>>,
3894    Vec<Option<String>>,
3895    bool, /* numeric */
3896) {
3897    let x_vec = x.as_vector();
3898    let y_vec = y.as_vector();
3899    let is_numeric = matches!(
3900        (x_vec, y_vec),
3901        (
3902            Some(Vector::Integer(_) | Vector::Double(_)),
3903            Some(Vector::Integer(_) | Vector::Double(_))
3904        )
3905    );
3906    let xc = x_vec.map(|v| v.to_characters()).unwrap_or_default();
3907    let yc = y_vec.map(|v| v.to_characters()).unwrap_or_default();
3908    (xc, yc, is_numeric)
3909}
3910
3911/// Reconstruct a numeric vector from character representations produced by set operations.
3912fn set_result_numeric(chars: Vec<Option<String>>) -> RValue {
3913    // Try integer first, fall back to double
3914    let as_ints: Option<Vec<Option<i64>>> = chars
3915        .iter()
3916        .map(|c| match c {
3917            None => Some(None),
3918            Some(s) => s.parse::<i64>().ok().map(Some),
3919        })
3920        .collect();
3921    if let Some(ints) = as_ints {
3922        return RValue::vec(Vector::Integer(ints.into()));
3923    }
3924    let doubles: Vec<Option<f64>> = chars
3925        .iter()
3926        .map(|c| c.as_ref().and_then(|s| s.parse::<f64>().ok()))
3927        .collect();
3928    RValue::vec(Vector::Double(doubles.into()))
3929}
3930
3931/// Set difference: elements in x that are not in y.
3932///
3933/// Works on character, integer, and double vectors. For numeric inputs,
3934/// returns a numeric vector; for character inputs, returns a character vector.
3935///
3936/// @param x vector
3937/// @param y vector
3938/// @return vector of elements in x but not y
3939#[builtin(min_args = 2)]
3940fn builtin_setdiff(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3941    let (x, y, numeric) = set_op_extract(&args[0], &args[1]);
3942    let y_keys: std::collections::HashSet<String> = y.iter().map(set_key).collect();
3943    let result: Vec<Option<String>> = x
3944        .into_iter()
3945        .filter(|xi| !y_keys.contains(&set_key(xi)))
3946        .collect();
3947    if numeric {
3948        Ok(set_result_numeric(result))
3949    } else {
3950        Ok(RValue::vec(Vector::Character(result.into())))
3951    }
3952}
3953
3954/// Set intersection: elements present in both x and y.
3955///
3956/// Works on character, integer, and double vectors. For numeric inputs,
3957/// returns a numeric vector; for character inputs, returns a character vector.
3958///
3959/// @param x vector
3960/// @param y vector
3961/// @return vector of common elements
3962#[builtin(min_args = 2)]
3963fn builtin_intersect(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3964    let (x, y, numeric) = set_op_extract(&args[0], &args[1]);
3965    let y_keys: std::collections::HashSet<String> = y.iter().map(set_key).collect();
3966    let result: Vec<Option<String>> = x
3967        .into_iter()
3968        .filter(|xi| y_keys.contains(&set_key(xi)))
3969        .collect();
3970    if numeric {
3971        Ok(set_result_numeric(result))
3972    } else {
3973        Ok(RValue::vec(Vector::Character(result.into())))
3974    }
3975}
3976
3977/// Set union: unique elements from both x and y.
3978///
3979/// Works on character, integer, and double vectors. For numeric inputs,
3980/// returns a numeric vector; for character inputs, returns a character vector.
3981///
3982/// @param x vector
3983/// @param y vector
3984/// @return vector of all unique elements
3985#[builtin(min_args = 2)]
3986fn builtin_union(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
3987    let (x, y, numeric) = set_op_extract(&args[0], &args[1]);
3988    let mut seen = std::collections::HashSet::new();
3989    let mut result = Vec::new();
3990    for xi in x.into_iter().chain(y) {
3991        let key = set_key(&xi);
3992        if seen.insert(key) {
3993            result.push(xi);
3994        }
3995    }
3996    if numeric {
3997        Ok(set_result_numeric(result))
3998    } else {
3999        Ok(RValue::vec(Vector::Character(result.into())))
4000    }
4001}
4002
4003/// Bin continuous values into intervals, returning a factor with interval labels.
4004///
4005/// @param x numeric vector of values to bin
4006/// @param breaks numeric vector of cut points (must be sorted, length >= 2)
4007/// @param labels optional character vector of labels (length = length(breaks) - 1)
4008/// @param include.lowest if TRUE, the lowest break is inclusive on the left
4009/// @param right if TRUE (default), intervals are (a,b]; if FALSE, [a,b)
4010/// @return integer vector with factor class and interval-label levels
4011#[builtin(min_args = 2)]
4012fn builtin_cut(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
4013    let ca = CallArgs::new(args, named);
4014
4015    let x_vals = ca
4016        .value("x", 0)
4017        .and_then(|v| v.as_vector().map(|v| v.to_doubles()))
4018        .ok_or_else(|| {
4019            RError::new(
4020                RErrorKind::Argument,
4021                "'x' must be a numeric vector".to_string(),
4022            )
4023        })?;
4024    let breaks = ca
4025        .value("breaks", 1)
4026        .and_then(|v| v.as_vector().map(|v| v.to_doubles()))
4027        .ok_or_else(|| {
4028            RError::new(
4029                RErrorKind::Argument,
4030                "'breaks' must be a numeric vector".to_string(),
4031            )
4032        })?;
4033
4034    let breaks: Vec<f64> = breaks.into_iter().flatten().collect();
4035    if breaks.len() < 2 {
4036        return Err(RError::new(
4037            RErrorKind::Argument,
4038            "'breaks' must have at least 2 values".to_string(),
4039        ));
4040    }
4041
4042    let right = ca.logical_flag("right", 4, true);
4043    let include_lowest = ca.logical_flag("include.lowest", 5, false);
4044
4045    let custom_labels: Option<Vec<String>> = ca.value("labels", 2).and_then(|v| {
4046        v.as_vector().and_then(|vec| {
4047            let chars = vec.to_characters();
4048            chars.into_iter().collect::<Option<Vec<String>>>()
4049        })
4050    });
4051
4052    let n_intervals = breaks.len() - 1;
4053
4054    // Generate default interval labels
4055    let labels: Vec<String> = if let Some(ref cl) = custom_labels {
4056        if cl.len() != n_intervals {
4057            return Err(RError::new(
4058                RErrorKind::Argument,
4059                format!(
4060                    "lengths of 'breaks' and 'labels' differ: {} breaks produce {} intervals but {} labels given",
4061                    breaks.len(),
4062                    n_intervals,
4063                    cl.len()
4064                ),
4065            ));
4066        }
4067        cl.clone()
4068    } else {
4069        (0..n_intervals)
4070            .map(|i| {
4071                let lo = breaks[i];
4072                let hi = breaks[i + 1];
4073                if right {
4074                    format!("({},{}]", format_r_double_cut(lo), format_r_double_cut(hi))
4075                } else {
4076                    format!("[{},{})", format_r_double_cut(lo), format_r_double_cut(hi))
4077                }
4078            })
4079            .collect()
4080    };
4081
4082    // Bin each value
4083    let codes: Vec<Option<i64>> = x_vals
4084        .iter()
4085        .map(|x| {
4086            let val = match x {
4087                Some(v) if v.is_finite() => *v,
4088                _ => return None, // NA or non-finite -> NA
4089            };
4090            for i in 0..n_intervals {
4091                let lo = breaks[i];
4092                let hi = breaks[i + 1];
4093                let in_bin = if right {
4094                    let lower_ok = if include_lowest && i == 0 {
4095                        val >= lo
4096                    } else {
4097                        val > lo
4098                    };
4099                    lower_ok && val <= hi
4100                } else {
4101                    let upper_ok = if include_lowest && i == n_intervals - 1 {
4102                        val <= hi
4103                    } else {
4104                        val < hi
4105                    };
4106                    val >= lo && upper_ok
4107                };
4108                if in_bin {
4109                    return Some(i64::try_from(i + 1).unwrap_or(0));
4110                }
4111            }
4112            None // outside all breaks
4113        })
4114        .collect();
4115
4116    let mut rv = RVector::from(Vector::Integer(codes.into()));
4117    rv.set_attr(
4118        "levels".to_string(),
4119        RValue::vec(Vector::Character(
4120            labels.into_iter().map(Some).collect::<Vec<_>>().into(),
4121        )),
4122    );
4123    rv.set_attr(
4124        "class".to_string(),
4125        RValue::vec(Vector::Character(vec![Some("factor".to_string())].into())),
4126    );
4127    Ok(RValue::Vector(rv))
4128}
4129
4130/// Format a double for cut() labels — avoid trailing zeros but keep precision.
4131fn format_r_double_cut(x: f64) -> String {
4132    if x == x.floor() && x.abs() < 1e15 {
4133        format!("{}", x as i64)
4134    } else {
4135        format!("{}", x)
4136    }
4137}
4138
4139/// Find the interval index containing each element of x.
4140///
4141/// For a sorted vector `vec` of length N, returns an integer vector of the same
4142/// length as `x`, where each value is in 0..N indicating which interval the
4143/// corresponding x value falls into (0 = before vec[0], N = after vec[N-1]).
4144///
4145/// @param x numeric vector
4146/// @param vec sorted numeric vector of break points
4147/// @return integer vector of interval indices
4148#[builtin(name = "findInterval", min_args = 2)]
4149fn builtin_find_interval(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
4150    let ca = CallArgs::new(args, named);
4151
4152    let x_vals = ca
4153        .value("x", 0)
4154        .and_then(|v| v.as_vector().map(|v| v.to_doubles()))
4155        .ok_or_else(|| {
4156            RError::new(
4157                RErrorKind::Argument,
4158                "'x' must be a numeric vector".to_string(),
4159            )
4160        })?;
4161    let vec_vals: Vec<f64> = ca
4162        .value("vec", 1)
4163        .and_then(|v| v.as_vector().map(|v| v.to_doubles()))
4164        .ok_or_else(|| {
4165            RError::new(
4166                RErrorKind::Argument,
4167                "'vec' must be a numeric vector".to_string(),
4168            )
4169        })?
4170        .into_iter()
4171        .flatten()
4172        .collect();
4173
4174    let result: Vec<Option<i64>> = x_vals
4175        .iter()
4176        .map(|x| {
4177            let val = (*x)?;
4178            // Binary search: find largest index i where vec_vals[i] <= val
4179            let mut lo = 0usize;
4180            let mut hi = vec_vals.len();
4181            while lo < hi {
4182                let mid = lo + (hi - lo) / 2;
4183                if vec_vals[mid] <= val {
4184                    lo = mid + 1;
4185                } else {
4186                    hi = mid;
4187                }
4188            }
4189            Some(i64::try_from(lo).unwrap_or(0))
4190        })
4191        .collect();
4192    Ok(RValue::vec(Vector::Integer(result.into())))
4193}
4194
4195/// Identify duplicate elements in a vector.
4196///
4197/// Returns TRUE for elements that have already appeared earlier in the vector.
4198///
4199/// @param x vector to check
4200/// @return logical vector of the same length
4201#[builtin(min_args = 1)]
4202fn builtin_duplicated(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4203    match args.first() {
4204        Some(RValue::Vector(v)) => {
4205            let chars = v.to_characters();
4206            let mut seen = Vec::new();
4207            let result: Vec<Option<bool>> = chars
4208                .iter()
4209                .map(|x| {
4210                    let key = format!("{:?}", x);
4211                    if seen.contains(&key) {
4212                        Some(true)
4213                    } else {
4214                        seen.push(key);
4215                        Some(false)
4216                    }
4217                })
4218                .collect();
4219            Ok(RValue::vec(Vector::Logical(result.into())))
4220        }
4221        _ => Ok(RValue::vec(Vector::Logical(vec![].into()))),
4222    }
4223}
4224
4225/// Get the interpreter's current working directory.
4226///
4227/// @return character scalar with the absolute path
4228#[interpreter_builtin]
4229fn interp_getwd(
4230    _args: &[RValue],
4231    _: &[(String, RValue)],
4232    context: &BuiltinContext,
4233) -> Result<RValue, RError> {
4234    let cwd =
4235        context.with_interpreter(|interp| interp.get_working_dir().to_string_lossy().to_string());
4236    Ok(RValue::vec(Vector::Character(vec![Some(cwd)].into())))
4237}
4238
4239/// Create a double vector of zeros with the given length.
4240///
4241/// Also aliased as `double`.
4242///
4243/// @param length number of elements (default: 0)
4244/// @return double vector initialized to 0.0
4245#[builtin(names = ["double"])]
4246fn builtin_numeric(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4247    let n = args
4248        .first()
4249        .and_then(|v| v.as_vector()?.as_integer_scalar())
4250        .map(usize::try_from)
4251        .transpose()?
4252        .unwrap_or(0);
4253    Ok(RValue::vec(Vector::Double(vec![Some(0.0); n].into())))
4254}
4255
4256/// Create an integer vector of zeros with the given length.
4257///
4258/// @param length number of elements (default: 0)
4259/// @return integer vector initialized to 0
4260#[builtin]
4261fn builtin_integer(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4262    let n = args
4263        .first()
4264        .and_then(|v| v.as_vector()?.as_integer_scalar())
4265        .map(usize::try_from)
4266        .transpose()?
4267        .unwrap_or(0);
4268    Ok(RValue::vec(Vector::Integer(vec![Some(0); n].into())))
4269}
4270
4271/// Create a logical vector of FALSE values with the given length.
4272///
4273/// @param length number of elements (default: 0)
4274/// @return logical vector initialized to FALSE
4275#[builtin]
4276fn builtin_logical(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4277    let n = args
4278        .first()
4279        .and_then(|v| v.as_vector()?.as_integer_scalar())
4280        .map(usize::try_from)
4281        .transpose()?
4282        .unwrap_or(0);
4283    Ok(RValue::vec(Vector::Logical(vec![Some(false); n].into())))
4284}
4285
4286/// Create a character vector of empty strings with the given length.
4287///
4288/// @param length number of elements (default: 0)
4289/// @return character vector initialized to ""
4290#[builtin]
4291fn builtin_character(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4292    let n = args
4293        .first()
4294        .and_then(|v| v.as_vector()?.as_integer_scalar())
4295        .map(usize::try_from)
4296        .transpose()?
4297        .unwrap_or(0);
4298    Ok(RValue::vec(Vector::Character(
4299        vec![Some(String::new()); n].into(),
4300    )))
4301}
4302
4303/// Create a matrix from the given data.
4304///
4305/// Fills column-by-column by default. Use `byrow = TRUE` for row-major fill.
4306///
4307/// @param data vector of values to fill the matrix
4308/// @param nrow number of rows
4309/// @param ncol number of columns
4310/// @param byrow if TRUE, fill by rows instead of columns
4311/// @param dimnames list of row and column name vectors
4312/// @return matrix (vector with dim and class attributes)
4313#[builtin]
4314fn builtin_matrix(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
4315    let data = args
4316        .first()
4317        .cloned()
4318        .unwrap_or(RValue::vec(Vector::Double(vec![Some(f64::NAN)].into())));
4319    let nrow_arg = named
4320        .iter()
4321        .find(|(n, _)| n == "nrow")
4322        .map(|(_, v)| v)
4323        .or(args.get(1))
4324        .and_then(|v| v.as_vector()?.as_integer_scalar());
4325    let ncol_arg = named
4326        .iter()
4327        .find(|(n, _)| n == "ncol")
4328        .map(|(_, v)| v)
4329        .or(args.get(2))
4330        .and_then(|v| v.as_vector()?.as_integer_scalar());
4331    let byrow = named
4332        .iter()
4333        .find(|(n, _)| n == "byrow")
4334        .map(|(_, v)| v)
4335        .or(args.get(3))
4336        .and_then(|v| v.as_vector()?.as_logical_scalar())
4337        .unwrap_or(false);
4338    let dimnames = named
4339        .iter()
4340        .find(|(n, _)| n == "dimnames")
4341        .map(|(_, v)| v)
4342        .or(args.get(4));
4343
4344    let data_inner = match &data {
4345        RValue::Vector(v) => &v.inner,
4346        _ => {
4347            return Err(RError::new(
4348                RErrorKind::Type,
4349                "data must be a vector".to_string(),
4350            ));
4351        }
4352    };
4353    let data_len = data_inner.len();
4354
4355    let (nrow, ncol) = match (nrow_arg, ncol_arg) {
4356        (Some(r), Some(c)) => (usize::try_from(r)?, usize::try_from(c)?),
4357        (Some(r), None) => {
4358            let r = usize::try_from(r)?;
4359            (r, if r > 0 { data_len.div_ceil(r) } else { 0 })
4360        }
4361        (None, Some(c)) => {
4362            let c = usize::try_from(c)?;
4363            (if c > 0 { data_len.div_ceil(c) } else { 0 }, c)
4364        }
4365        (None, None) => (data_len, 1),
4366    };
4367
4368    let total = nrow * ncol;
4369    // Build index mapping: source index for each element in the result
4370    let indices: Vec<usize> = if byrow {
4371        (0..nrow)
4372            .flat_map(|i| (0..ncol).map(move |j| (i * ncol + j) % data_len))
4373            .collect()
4374    } else {
4375        (0..total).map(|idx| idx % data_len).collect()
4376    };
4377
4378    let mat_vec = data_inner.select_indices(&indices);
4379    let mut rv = RVector::from(mat_vec);
4380    rv.set_attr(
4381        "class".to_string(),
4382        RValue::vec(Vector::Character(
4383            vec![Some("matrix".to_string()), Some("array".to_string())].into(),
4384        )),
4385    );
4386    rv.set_attr(
4387        "dim".to_string(),
4388        RValue::vec(Vector::Integer(
4389            vec![Some(i64::try_from(nrow)?), Some(i64::try_from(ncol)?)].into(),
4390        )),
4391    );
4392    if let Some(dimnames) = dimnames {
4393        if !dimnames.is_null() {
4394            rv.set_attr("dimnames".to_string(), dimnames.clone());
4395        }
4396    }
4397    Ok(RValue::Vector(rv))
4398}
4399
4400/// Get the dimensions of a matrix, array, or data frame.
4401///
4402/// @param x object to query
4403/// @return integer vector of dimensions, or NULL if none
4404#[builtin(min_args = 1)]
4405fn builtin_dim(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4406    match args.first() {
4407        Some(RValue::Vector(rv)) => Ok(rv.get_attr("dim").cloned().unwrap_or(RValue::Null)),
4408        Some(value @ RValue::List(l)) if has_class(value, "data.frame") => {
4409            Ok(RValue::vec(Vector::Integer(
4410                vec![
4411                    Some(i64::try_from(data_frame_row_count(l))?),
4412                    Some(i64::try_from(l.values.len())?),
4413                ]
4414                .into(),
4415            )))
4416        }
4417        Some(RValue::List(l)) => Ok(l.get_attr("dim").cloned().unwrap_or(RValue::Null)),
4418        _ => Ok(RValue::Null),
4419    }
4420}
4421
4422/// Set the dimensions of an object, converting it to a matrix or array.
4423///
4424/// @param x vector to reshape
4425/// @param value integer vector of new dimensions, or NULL to remove
4426/// @return the reshaped object
4427#[builtin(name = "dim<-", min_args = 2)]
4428fn builtin_dim_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4429    let dim_val = args.get(1).cloned().unwrap_or(RValue::Null);
4430    match args.first() {
4431        Some(RValue::Vector(rv)) => {
4432            let mut rv = rv.clone();
4433            if dim_val.is_null() {
4434                rv.attrs.as_mut().map(|a| a.shift_remove("dim"));
4435                rv.attrs.as_mut().map(|a| a.shift_remove("class"));
4436            } else {
4437                rv.set_attr("dim".to_string(), dim_val);
4438                rv.set_attr(
4439                    "class".to_string(),
4440                    RValue::vec(Vector::Character(
4441                        vec![Some("matrix".to_string()), Some("array".to_string())].into(),
4442                    )),
4443                );
4444            }
4445            Ok(RValue::Vector(rv))
4446        }
4447        other => Ok(other.cloned().unwrap_or(RValue::Null)),
4448    }
4449}
4450
4451/// Get the number of rows of a matrix, array, or data frame.
4452///
4453/// @param x object to query
4454/// @return integer scalar, or NULL for objects without dimensions
4455#[builtin(min_args = 1)]
4456fn builtin_nrow(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4457    match args.first() {
4458        Some(RValue::Vector(rv)) => {
4459            if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
4460                if !dims.is_empty() {
4461                    return Ok(RValue::vec(Vector::Integer(vec![dims[0]].into())));
4462                }
4463            }
4464            Ok(RValue::Null)
4465        }
4466        Some(RValue::List(l)) => {
4467            if let Some(dims) = get_dim_ints(l.get_attr("dim")) {
4468                if !dims.is_empty() {
4469                    return Ok(RValue::vec(Vector::Integer(vec![dims[0]].into())));
4470                }
4471            }
4472            if has_class(&args[0], "data.frame") {
4473                if let Some(rn) = l.get_attr("row.names") {
4474                    return Ok(RValue::vec(Vector::Integer(
4475                        vec![Some(i64::try_from(rn.length())?)].into(),
4476                    )));
4477                }
4478                // data frame with columns but no row.names — use first column length
4479                let n = l.values.first().map(|(_, v)| v.length()).unwrap_or(0);
4480                return Ok(RValue::vec(Vector::Integer(
4481                    vec![Some(i64::try_from(n)?)].into(),
4482                )));
4483            }
4484            Ok(RValue::Null)
4485        }
4486        _ => Ok(RValue::Null),
4487    }
4488}
4489
4490/// Get the number of columns of a matrix, array, or data frame.
4491///
4492/// @param x object to query
4493/// @return integer scalar, or NULL for objects without dimensions
4494#[builtin(min_args = 1)]
4495fn builtin_ncol(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4496    match args.first() {
4497        Some(RValue::Vector(rv)) => {
4498            if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
4499                if dims.len() >= 2 {
4500                    return Ok(RValue::vec(Vector::Integer(vec![dims[1]].into())));
4501                }
4502            }
4503            Ok(RValue::Null)
4504        }
4505        Some(RValue::List(l)) => {
4506            if let Some(dims) = get_dim_ints(l.get_attr("dim")) {
4507                if dims.len() >= 2 {
4508                    return Ok(RValue::vec(Vector::Integer(vec![dims[1]].into())));
4509                }
4510            }
4511            if has_class(
4512                args.first().expect("min_args = 1 guarantees first arg"),
4513                "data.frame",
4514            ) {
4515                return Ok(RValue::vec(Vector::Integer(
4516                    vec![Some(i64::try_from(l.values.len())?)].into(),
4517                )));
4518            }
4519            Ok(RValue::Null)
4520        }
4521        _ => Ok(RValue::Null),
4522    }
4523}
4524
4525/// Get the number of rows, treating non-matrix vectors as 1-column matrices.
4526///
4527/// Unlike `nrow()`, returns the vector length instead of NULL for plain vectors.
4528///
4529/// @param x object to query
4530/// @return integer scalar
4531#[builtin(name = "NROW", min_args = 1)]
4532fn builtin_nrow_safe(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4533    match args.first() {
4534        Some(RValue::Vector(rv)) => {
4535            if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
4536                if !dims.is_empty() {
4537                    return Ok(RValue::vec(Vector::Integer(vec![dims[0]].into())));
4538                }
4539            }
4540            Ok(RValue::vec(Vector::Integer(
4541                vec![Some(i64::try_from(rv.len())?)].into(),
4542            )))
4543        }
4544        Some(RValue::List(l)) => {
4545            if let Some(dims) = get_dim_ints(l.get_attr("dim")) {
4546                if !dims.is_empty() {
4547                    return Ok(RValue::vec(Vector::Integer(vec![dims[0]].into())));
4548                }
4549            }
4550            // Data frame: nrow = length of first column
4551            if has_class(
4552                args.first().expect("min_args = 1 guarantees first arg"),
4553                "data.frame",
4554            ) {
4555                if let Some(rn) = l.get_attr("row.names") {
4556                    return Ok(RValue::vec(Vector::Integer(
4557                        vec![Some(i64::try_from(rn.length())?)].into(),
4558                    )));
4559                }
4560                let n = l.values.first().map(|(_, v)| v.length()).unwrap_or(0);
4561                return Ok(RValue::vec(Vector::Integer(
4562                    vec![Some(i64::try_from(n)?)].into(),
4563                )));
4564            }
4565            Ok(RValue::vec(Vector::Integer(
4566                vec![Some(i64::try_from(l.values.len())?)].into(),
4567            )))
4568        }
4569        Some(RValue::Null) => Ok(RValue::vec(Vector::Integer(vec![Some(0)].into()))),
4570        _ => Ok(RValue::vec(Vector::Integer(vec![Some(1)].into()))),
4571    }
4572}
4573
4574/// Get the number of columns, treating non-matrix vectors as 1-column.
4575///
4576/// Unlike `ncol()`, returns 1 instead of NULL for plain vectors.
4577///
4578/// @param x object to query
4579/// @return integer scalar
4580#[builtin(name = "NCOL", min_args = 1)]
4581fn builtin_ncol_safe(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4582    match args.first() {
4583        Some(RValue::Vector(rv)) => {
4584            if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
4585                if dims.len() >= 2 {
4586                    return Ok(RValue::vec(Vector::Integer(vec![dims[1]].into())));
4587                }
4588            }
4589            Ok(RValue::vec(Vector::Integer(vec![Some(1)].into())))
4590        }
4591        Some(RValue::List(l)) => {
4592            if let Some(dims) = get_dim_ints(l.get_attr("dim")) {
4593                if dims.len() >= 2 {
4594                    return Ok(RValue::vec(Vector::Integer(vec![dims[1]].into())));
4595                }
4596            }
4597            if has_class(
4598                args.first().expect("min_args = 1 guarantees first arg"),
4599                "data.frame",
4600            ) {
4601                return Ok(RValue::vec(Vector::Integer(
4602                    vec![Some(i64::try_from(l.values.len())?)].into(),
4603                )));
4604            }
4605            Ok(RValue::vec(Vector::Integer(vec![Some(1)].into())))
4606        }
4607        Some(RValue::Null) => Ok(RValue::vec(Vector::Integer(vec![Some(0)].into()))),
4608        _ => Ok(RValue::vec(Vector::Integer(vec![Some(1)].into()))),
4609    }
4610}
4611
4612// t() is in math.rs (type-preserving implementation)
4613
4614/// Remove names and dimnames from an object.
4615///
4616/// @param obj object to strip names from
4617/// @return the object with names and dimnames attributes removed
4618#[builtin(min_args = 1)]
4619fn builtin_unname(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4620    match args.first() {
4621        Some(RValue::Vector(rv)) => {
4622            let mut rv = rv.clone();
4623            rv.attrs.as_mut().map(|a| a.shift_remove("names"));
4624            rv.attrs.as_mut().map(|a| a.shift_remove("dimnames"));
4625            Ok(RValue::Vector(rv))
4626        }
4627        Some(RValue::List(l)) => {
4628            let mut l = l.clone();
4629            for entry in &mut l.values {
4630                entry.0 = None;
4631            }
4632            l.attrs.as_mut().map(|a| a.shift_remove("names"));
4633            l.attrs.as_mut().map(|a| a.shift_remove("dimnames"));
4634            Ok(RValue::List(l))
4635        }
4636        other => Ok(other.cloned().unwrap_or(RValue::Null)),
4637    }
4638}
4639
4640/// Get the dimension names of a matrix, array, or data frame.
4641///
4642/// @param x object to query
4643/// @return list of character vectors (one per dimension), or NULL
4644#[builtin(min_args = 1)]
4645fn builtin_dimnames(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4646    match args.first() {
4647        Some(value @ RValue::List(list)) if has_class(value, "data.frame") => {
4648            data_frame_dimnames_value(list)
4649        }
4650        Some(RValue::Vector(rv)) => Ok(rv.get_attr("dimnames").cloned().unwrap_or(RValue::Null)),
4651        Some(RValue::List(l)) => Ok(l.get_attr("dimnames").cloned().unwrap_or(RValue::Null)),
4652        _ => Ok(RValue::Null),
4653    }
4654}
4655
4656/// Set the dimension names of a matrix, array, or data frame.
4657///
4658/// @param x object to modify
4659/// @param value list of character vectors (one per dimension), or NULL
4660/// @return the modified object
4661#[builtin(name = "dimnames<-", min_args = 2)]
4662fn builtin_dimnames_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4663    let dimnames_val = args.get(1).cloned().unwrap_or(RValue::Null);
4664    match args.first() {
4665        Some(RValue::Vector(rv)) => {
4666            let mut rv = rv.clone();
4667            if dimnames_val.is_null() {
4668                rv.attrs.as_mut().map(|a| a.shift_remove("dimnames"));
4669            } else {
4670                rv.set_attr("dimnames".to_string(), dimnames_val);
4671            }
4672            Ok(RValue::Vector(rv))
4673        }
4674        Some(value @ RValue::List(l)) if has_class(value, "data.frame") => {
4675            let mut l = l.clone();
4676            set_data_frame_dimnames(&mut l, &dimnames_val)?;
4677            Ok(RValue::List(l))
4678        }
4679        Some(RValue::List(l)) => {
4680            let mut l = l.clone();
4681            if dimnames_val.is_null() {
4682                l.attrs.as_mut().map(|a| a.shift_remove("dimnames"));
4683            } else {
4684                l.set_attr("dimnames".to_string(), dimnames_val);
4685            }
4686            Ok(RValue::List(l))
4687        }
4688        other => Ok(other.cloned().unwrap_or(RValue::Null)),
4689    }
4690}
4691
4692/// Create a multi-dimensional array.
4693///
4694/// @param data vector of values to fill the array (recycled if needed)
4695/// @param dim integer vector of dimensions
4696/// @param dimnames list of dimension name vectors
4697/// @return array (vector with dim and class attributes)
4698#[builtin]
4699fn builtin_array(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
4700    // array(data = NA, dim = length(data), dimnames = NULL)
4701    let data = args
4702        .first()
4703        .cloned()
4704        .unwrap_or(RValue::vec(Vector::Logical(vec![None].into())));
4705    let dim_arg = named
4706        .iter()
4707        .find(|(n, _)| n == "dim")
4708        .map(|(_, v)| v)
4709        .or(args.get(1));
4710    let dimnames_arg = named
4711        .iter()
4712        .find(|(n, _)| n == "dimnames")
4713        .map(|(_, v)| v)
4714        .or(args.get(2));
4715
4716    let data_vec = match &data {
4717        RValue::Vector(v) => v.to_doubles(),
4718        RValue::Null => vec![],
4719        _ => vec![Some(f64::NAN)],
4720    };
4721
4722    // Parse dim: can be a single integer or a vector of integers
4723    let dims: Vec<usize> = match dim_arg {
4724        Some(val) => {
4725            let ints = match val.as_vector() {
4726                Some(v) => v.to_integers(),
4727                None => {
4728                    return Err(RError::new(
4729                        RErrorKind::Argument,
4730                        "'dim' must be a numeric vector".to_string(),
4731                    ))
4732                }
4733            };
4734            ints.iter()
4735                .map(|x| usize::try_from(x.unwrap_or(0)))
4736                .collect::<Result<Vec<_>, _>>()?
4737        }
4738        None => vec![data_vec.len()],
4739    };
4740
4741    // Calculate total elements
4742    let total: usize = dims.iter().product();
4743
4744    // Recycle data to fill the array
4745    let mut mat = Vec::with_capacity(total);
4746    if data_vec.is_empty() {
4747        mat.resize(total, None);
4748    } else {
4749        for i in 0..total {
4750            mat.push(data_vec[i % data_vec.len()]);
4751        }
4752    }
4753
4754    let mut rv = RVector::from(Vector::Double(mat.into()));
4755
4756    // Set class: arrays with 2 dims get "matrix" + "array", others just "array"
4757    if dims.len() == 2 {
4758        rv.set_attr(
4759            "class".to_string(),
4760            RValue::vec(Vector::Character(
4761                vec![Some("matrix".to_string()), Some("array".to_string())].into(),
4762            )),
4763        );
4764    } else {
4765        rv.set_attr(
4766            "class".to_string(),
4767            RValue::vec(Vector::Character(vec![Some("array".to_string())].into())),
4768        );
4769    }
4770
4771    // Set dim attribute
4772    rv.set_attr(
4773        "dim".to_string(),
4774        RValue::vec(Vector::Integer(
4775            dims.iter()
4776                .map(|&d| i64::try_from(d).map(Some))
4777                .collect::<Result<Vec<_>, _>>()?
4778                .into(),
4779        )),
4780    );
4781
4782    // Set dimnames if provided
4783    if let Some(dn) = dimnames_arg {
4784        if !dn.is_null() {
4785            rv.set_attr("dimnames".to_string(), dn.clone());
4786        }
4787    }
4788
4789    Ok(RValue::Vector(rv))
4790}
4791
4792/// Transpose or permute the dimensions of an array.
4793///
4794/// For 2D arrays (matrices), this is equivalent to `t()`. For higher-dimensional
4795/// arrays, `perm` specifies the new ordering of dimensions. By default, `perm`
4796/// reverses the dimensions (i.e. for a 3D array with dims (a,b,c), the default
4797/// permutation is c(3,2,1) giving dims (c,b,a)).
4798///
4799/// @param a an array (vector with dim attribute)
4800/// @param perm integer vector specifying the new dimension order (1-based)
4801/// @return array with permuted dimensions
4802#[builtin(min_args = 1)]
4803fn builtin_aperm(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
4804    let x = args.first().ok_or_else(|| {
4805        RError::new(
4806            RErrorKind::Argument,
4807            "aperm() requires an array argument".to_string(),
4808        )
4809    })?;
4810
4811    let rv = match x {
4812        RValue::Vector(rv) => rv,
4813        _ => {
4814            return Err(RError::new(
4815                RErrorKind::Type,
4816                "aperm() requires an array (a vector with dim attribute)".to_string(),
4817            ))
4818        }
4819    };
4820
4821    // Extract dimensions
4822    let dims: Vec<usize> = match get_dim_ints(rv.get_attr("dim")) {
4823        Some(dim_ints) => dim_ints
4824            .iter()
4825            .map(|x| usize::try_from(x.unwrap_or(0)))
4826            .collect::<Result<Vec<_>, _>>()?,
4827        None => {
4828            return Err(RError::new(
4829                RErrorKind::Argument,
4830                "aperm() requires an array with a 'dim' attribute".to_string(),
4831            ))
4832        }
4833    };
4834
4835    let ndim = dims.len();
4836
4837    // Parse perm argument: either named or positional
4838    let perm_arg = named
4839        .iter()
4840        .find(|(n, _)| n == "perm")
4841        .map(|(_, v)| v)
4842        .or(args.get(1));
4843
4844    // perm is 1-based in R, convert to 0-based
4845    let perm: Vec<usize> = match perm_arg {
4846        Some(val) => {
4847            let ints = match val.as_vector() {
4848                Some(v) => v.to_integers(),
4849                None => {
4850                    return Err(RError::new(
4851                        RErrorKind::Argument,
4852                        "'perm' must be a numeric vector".to_string(),
4853                    ))
4854                }
4855            };
4856            if ints.len() != ndim {
4857                return Err(RError::new(
4858                    RErrorKind::Argument,
4859                    format!(
4860                        "'perm' must have length {} (matching the number of dimensions), got {}",
4861                        ndim,
4862                        ints.len()
4863                    ),
4864                ));
4865            }
4866            let mut p = Vec::with_capacity(ndim);
4867            for &v in &ints {
4868                let idx = usize::try_from(v.unwrap_or(0))?;
4869                if idx < 1 || idx > ndim {
4870                    return Err(RError::new(
4871                        RErrorKind::Argument,
4872                        format!("perm values must be between 1 and {}, got {}", ndim, idx),
4873                    ));
4874                }
4875                p.push(idx - 1);
4876            }
4877            p
4878        }
4879        None => {
4880            // Default: reverse dimensions
4881            (0..ndim).rev().collect()
4882        }
4883    };
4884
4885    // Validate perm is a valid permutation (all dimensions appear exactly once)
4886    let mut seen = vec![false; ndim];
4887    for &p in &perm {
4888        if seen[p] {
4889            return Err(RError::new(
4890                RErrorKind::Argument,
4891                "perm must be a permutation of 1:n where n is the number of dimensions".to_string(),
4892            ));
4893        }
4894        seen[p] = true;
4895    }
4896
4897    let total: usize = dims.iter().product();
4898    let data = rv.to_doubles();
4899
4900    // Compute new dimensions
4901    let new_dims: Vec<usize> = perm.iter().map(|&p| dims[p]).collect();
4902
4903    // Compute strides for original array (column-major / Fortran order)
4904    let mut old_strides = vec![1usize; ndim];
4905    for d in 1..ndim {
4906        old_strides[d] = old_strides[d - 1] * dims[d - 1];
4907    }
4908
4909    // Compute strides for new array
4910    let mut new_strides = vec![1usize; ndim];
4911    for d in 1..ndim {
4912        new_strides[d] = new_strides[d - 1] * new_dims[d - 1];
4913    }
4914
4915    // Permute: for each position in the new array, compute the corresponding
4916    // position in the old array
4917    let mut result = vec![None; total];
4918    for (new_flat, slot) in result.iter_mut().enumerate() {
4919        // Decompose new_flat into new multi-index
4920        let mut remainder = new_flat;
4921        let mut new_idx = vec![0usize; ndim];
4922        for d in (0..ndim).rev() {
4923            new_idx[d] = remainder / new_strides[d];
4924            remainder %= new_strides[d];
4925        }
4926
4927        // Map to old multi-index via inverse permutation
4928        let mut old_flat = 0;
4929        for d in 0..ndim {
4930            old_flat += new_idx[d] * old_strides[perm[d]];
4931        }
4932
4933        *slot = data[old_flat];
4934    }
4935
4936    let mut out = RVector::from(Vector::Double(result.into()));
4937
4938    // Set dim attribute
4939    out.set_attr(
4940        "dim".to_string(),
4941        RValue::vec(Vector::Integer(
4942            new_dims
4943                .iter()
4944                .map(|&d| i64::try_from(d).map(Some))
4945                .collect::<Result<Vec<_>, _>>()?
4946                .into(),
4947        )),
4948    );
4949
4950    // Set class attribute
4951    if new_dims.len() == 2 {
4952        out.set_attr(
4953            "class".to_string(),
4954            RValue::vec(Vector::Character(
4955                vec![Some("matrix".to_string()), Some("array".to_string())].into(),
4956            )),
4957        );
4958    } else {
4959        out.set_attr(
4960            "class".to_string(),
4961            RValue::vec(Vector::Character(vec![Some("array".to_string())].into())),
4962        );
4963    }
4964
4965    // Permute dimnames if present
4966    if let Some(RValue::List(dimnames_list)) = rv.get_attr("dimnames") {
4967        let mut new_dimnames_vals = Vec::with_capacity(ndim);
4968        for &p in &perm {
4969            if p < dimnames_list.values.len() {
4970                new_dimnames_vals.push(dimnames_list.values[p].clone());
4971            } else {
4972                new_dimnames_vals.push((None, RValue::Null));
4973            }
4974        }
4975        out.set_attr(
4976            "dimnames".to_string(),
4977            RValue::List(RList::new(new_dimnames_vals)),
4978        );
4979    }
4980
4981    Ok(RValue::Vector(out))
4982}
4983
4984/// Bind vectors, matrices, or data frames by rows.
4985///
4986/// Combines arguments row-wise. If any argument is a data frame, produces
4987/// a data frame result by matching columns by name. Otherwise produces a matrix.
4988///
4989/// @param ... vectors, matrices, or data frames to bind
4990/// @return matrix or data frame
4991#[builtin(min_args = 1)]
4992fn builtin_rbind(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
4993    if args.is_empty() {
4994        return Ok(RValue::Null);
4995    }
4996
4997    // Check if any argument is a data frame — if so, use data frame rbind
4998    let has_df = args
4999        .iter()
5000        .any(|a| matches!(a, RValue::List(_)) && has_class(a, "data.frame"));
5001    if has_df {
5002        return rbind_data_frames(args);
5003    }
5004
5005    let mut inputs = Vec::new();
5006    for arg in args {
5007        match arg {
5008            RValue::Vector(rv) => {
5009                let data = rv.to_doubles();
5010                if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
5011                    if dims.len() >= 2 {
5012                        let nr = usize::try_from(dims[0].unwrap_or(0))?;
5013                        let nc = usize::try_from(dims[1].unwrap_or(0))?;
5014                        inputs.push(BindInput {
5015                            data,
5016                            nrow: nr,
5017                            ncol: nc,
5018                            row_names: dimnames_component(rv.get_attr("dimnames"), 0),
5019                            col_names: dimnames_component(rv.get_attr("dimnames"), 1),
5020                        });
5021                        continue;
5022                    }
5023                }
5024                // Plain vector becomes a 1-row matrix
5025                let len = data.len();
5026                inputs.push(BindInput {
5027                    data,
5028                    nrow: 1,
5029                    ncol: len,
5030                    row_names: None,
5031                    col_names: rv.get_attr("names").and_then(coerce_name_values),
5032                });
5033            }
5034            RValue::Null => continue,
5035            _ => {
5036                return Err(RError::new(
5037                    RErrorKind::Argument,
5038                    "cannot rbind non-vector/matrix arguments".to_string(),
5039                ))
5040            }
5041        }
5042    }
5043
5044    if inputs.is_empty() {
5045        return Ok(RValue::Null);
5046    }
5047
5048    // All inputs must have the same number of columns (after recycling)
5049    let max_ncol = inputs.iter().map(|input| input.ncol).max().unwrap_or(0);
5050    if max_ncol == 0 {
5051        return Ok(RValue::Null);
5052    }
5053
5054    // Check column compatibility
5055    for input in &inputs {
5056        if input.ncol != max_ncol && max_ncol % input.ncol != 0 && input.ncol % max_ncol != 0 {
5057            return Err(RError::new(
5058                RErrorKind::Argument,
5059                "number of columns of arguments do not match".to_string(),
5060            ));
5061        }
5062    }
5063
5064    // Total rows
5065    let total_nrow: usize = inputs.iter().map(|input| input.nrow).sum();
5066
5067    // Build result column-major: for each column j, concatenate rows from all inputs
5068    let mut result = Vec::with_capacity(total_nrow * max_ncol);
5069    for j in 0..max_ncol {
5070        for input in &inputs {
5071            let actual_j = j % input.ncol;
5072            for i in 0..input.nrow {
5073                // Column-major index: col * nrow + row
5074                let idx = actual_j * input.nrow + i;
5075                result.push(if idx < input.data.len() {
5076                    input.data[idx]
5077                } else {
5078                    None
5079                });
5080            }
5081        }
5082    }
5083
5084    let mut row_names = Vec::new();
5085    let has_any_row_names = inputs.iter().any(|input| input.row_names.is_some());
5086    if has_any_row_names {
5087        for input in &inputs {
5088            if let Some(names) = &input.row_names {
5089                row_names.extend(
5090                    (0..input.nrow)
5091                        .map(|idx| names.get(idx).cloned().unwrap_or(None))
5092                        .collect::<Vec<_>>(),
5093                );
5094            } else {
5095                row_names.extend(std::iter::repeat_n(None, input.nrow));
5096            }
5097        }
5098    }
5099
5100    let mut col_names = Vec::new();
5101    if let Some(source_names) = inputs.iter().find_map(|input| input.col_names.clone()) {
5102        col_names.extend(
5103            (0..max_ncol)
5104                .map(|idx| {
5105                    source_names
5106                        .get(idx % source_names.len())
5107                        .cloned()
5108                        .unwrap_or(None)
5109                })
5110                .collect::<Vec<_>>(),
5111        );
5112    }
5113
5114    let mut rv = RVector::from(Vector::Double(result.into()));
5115    rv.set_attr(
5116        "class".to_string(),
5117        RValue::vec(Vector::Character(
5118            vec![Some("matrix".to_string()), Some("array".to_string())].into(),
5119        )),
5120    );
5121    rv.set_attr(
5122        "dim".to_string(),
5123        RValue::vec(Vector::Integer(
5124            vec![
5125                Some(i64::try_from(total_nrow)?),
5126                Some(i64::try_from(max_ncol)?),
5127            ]
5128            .into(),
5129        )),
5130    );
5131    if let Some(dimnames) = bind_dimnames_value(row_names, col_names) {
5132        rv.set_attr("dimnames".to_string(), dimnames);
5133    }
5134    Ok(RValue::Vector(rv))
5135}
5136
5137/// Row-bind data frames by matching column names.
5138fn rbind_data_frames(args: &[RValue]) -> Result<RValue, RError> {
5139    // Collect all unique column names in order
5140    let mut all_col_names: Vec<String> = Vec::new();
5141    let mut seen = std::collections::HashSet::new();
5142    let mut dfs: Vec<&RList> = Vec::new();
5143
5144    for arg in args {
5145        match arg {
5146            RValue::List(list) if has_class(arg, "data.frame") => {
5147                let names = dataframes::df_col_names(list);
5148                for name in names.into_iter().flatten() {
5149                    if seen.insert(name.clone()) {
5150                        all_col_names.push(name);
5151                    }
5152                }
5153                dfs.push(list);
5154            }
5155            RValue::Null => continue,
5156            _ => {
5157                return Err(RError::new(
5158                    RErrorKind::Argument,
5159                    "rbind() with data frames requires all arguments to be data frames".to_string(),
5160                ))
5161            }
5162        }
5163    }
5164
5165    if dfs.is_empty() {
5166        return Ok(RValue::Null);
5167    }
5168
5169    let total_nrow: usize = dfs.iter().map(|df| dataframes::df_nrow(df)).sum();
5170
5171    // For each column, concatenate values from all data frames
5172    let mut output_columns: Vec<(Option<String>, RValue)> = Vec::new();
5173    for col_name in &all_col_names {
5174        let mut parts: Vec<RValue> = Vec::new();
5175        for df in &dfs {
5176            let col_idx = dataframes::df_col_index(df, col_name);
5177            let nrow = dataframes::df_nrow(df);
5178            if let Some(idx) = col_idx {
5179                parts.push(df.values[idx].1.clone());
5180            } else {
5181                // Fill with NA for missing columns
5182                parts.push(RValue::vec(Vector::Logical(vec![None; nrow].into())));
5183            }
5184        }
5185        let combined = concat_column_values(&parts)?;
5186        output_columns.push((Some(col_name.clone()), combined));
5187    }
5188
5189    dataframes::build_data_frame(output_columns, total_nrow)
5190}
5191
5192/// Concatenate column values from multiple data frames into a single column.
5193fn concat_column_values(parts: &[RValue]) -> Result<RValue, RError> {
5194    // Determine the "widest" type
5195    let mut has_character = false;
5196    let mut has_double = false;
5197    let mut has_integer = false;
5198    let mut has_logical = false;
5199
5200    for part in parts {
5201        if let RValue::Vector(rv) = part {
5202            match &rv.inner {
5203                Vector::Character(_) => has_character = true,
5204                Vector::Double(_) => has_double = true,
5205                Vector::Integer(_) => has_integer = true,
5206                Vector::Logical(_) => has_logical = true,
5207                _ => {}
5208            }
5209        }
5210    }
5211
5212    if has_character {
5213        let mut result: Vec<Option<String>> = Vec::new();
5214        for part in parts {
5215            if let RValue::Vector(rv) = part {
5216                result.extend(rv.inner.to_characters());
5217            }
5218        }
5219        Ok(RValue::vec(Vector::Character(result.into())))
5220    } else if has_double {
5221        let mut result: Vec<Option<f64>> = Vec::new();
5222        for part in parts {
5223            if let RValue::Vector(rv) = part {
5224                result.extend(rv.to_doubles());
5225            }
5226        }
5227        Ok(RValue::vec(Vector::Double(result.into())))
5228    } else if has_integer {
5229        let mut result: Vec<Option<i64>> = Vec::new();
5230        for part in parts {
5231            if let RValue::Vector(rv) = part {
5232                result.extend(rv.inner.to_integers());
5233            }
5234        }
5235        Ok(RValue::vec(Vector::Integer(result.into())))
5236    } else if has_logical {
5237        let mut result: Vec<Option<bool>> = Vec::new();
5238        for part in parts {
5239            if let RValue::Vector(rv) = part {
5240                result.extend(rv.inner.to_logicals());
5241            }
5242        }
5243        Ok(RValue::vec(Vector::Logical(result.into())))
5244    } else {
5245        Ok(RValue::Null)
5246    }
5247}
5248
5249/// Bind vectors, matrices, or data frames by columns.
5250///
5251/// Combines arguments column-wise. If any argument is a data frame, produces
5252/// a data frame result. Otherwise produces a matrix.
5253///
5254/// @param ... vectors, matrices, or data frames to bind
5255/// @return matrix or data frame
5256#[builtin(min_args = 1)]
5257fn builtin_cbind(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5258    if args.is_empty() {
5259        return Ok(RValue::Null);
5260    }
5261
5262    // Check if any argument is a data frame — if so, use data frame cbind
5263    let has_df = args
5264        .iter()
5265        .any(|a| matches!(a, RValue::List(_)) && has_class(a, "data.frame"));
5266    if has_df {
5267        return cbind_data_frames(args);
5268    }
5269
5270    let mut inputs = Vec::new();
5271    for arg in args {
5272        match arg {
5273            RValue::Vector(rv) => {
5274                let data = rv.to_doubles();
5275                if let Some(dims) = get_dim_ints(rv.get_attr("dim")) {
5276                    if dims.len() >= 2 {
5277                        let nr = usize::try_from(dims[0].unwrap_or(0))?;
5278                        let nc = usize::try_from(dims[1].unwrap_or(0))?;
5279                        inputs.push(BindInput {
5280                            data,
5281                            nrow: nr,
5282                            ncol: nc,
5283                            row_names: dimnames_component(rv.get_attr("dimnames"), 0),
5284                            col_names: dimnames_component(rv.get_attr("dimnames"), 1),
5285                        });
5286                        continue;
5287                    }
5288                }
5289                // Plain vector becomes a 1-column matrix
5290                let len = data.len();
5291                inputs.push(BindInput {
5292                    data,
5293                    nrow: len,
5294                    ncol: 1,
5295                    row_names: rv.get_attr("names").and_then(coerce_name_values),
5296                    col_names: None,
5297                });
5298            }
5299            RValue::Null => continue,
5300            _ => {
5301                return Err(RError::new(
5302                    RErrorKind::Argument,
5303                    "cannot cbind non-vector/matrix arguments".to_string(),
5304                ))
5305            }
5306        }
5307    }
5308
5309    if inputs.is_empty() {
5310        return Ok(RValue::Null);
5311    }
5312
5313    // All inputs must have the same number of rows (after recycling)
5314    let max_nrow = inputs.iter().map(|input| input.nrow).max().unwrap_or(0);
5315    if max_nrow == 0 {
5316        return Ok(RValue::Null);
5317    }
5318
5319    // Check row compatibility
5320    for input in &inputs {
5321        if input.nrow != max_nrow && max_nrow % input.nrow != 0 && input.nrow % max_nrow != 0 {
5322            return Err(RError::new(
5323                RErrorKind::Argument,
5324                "number of rows of arguments do not match".to_string(),
5325            ));
5326        }
5327    }
5328
5329    // Total columns
5330    let total_ncol: usize = inputs.iter().map(|input| input.ncol).sum();
5331
5332    // Build result column-major: for each input, append its columns (recycling rows)
5333    let mut result = Vec::with_capacity(max_nrow * total_ncol);
5334    for input in &inputs {
5335        for j in 0..input.ncol {
5336            for i in 0..max_nrow {
5337                // Recycle: wrap row index within the input's actual nrow
5338                let actual_i = i % input.nrow;
5339                let idx = j * input.nrow + actual_i;
5340                result.push(if idx < input.data.len() {
5341                    input.data[idx]
5342                } else {
5343                    None
5344                });
5345            }
5346        }
5347    }
5348
5349    let row_names =
5350        if let Some(source_names) = inputs.iter().find_map(|input| input.row_names.clone()) {
5351            (0..max_nrow)
5352                .map(|idx| {
5353                    source_names
5354                        .get(idx % source_names.len())
5355                        .cloned()
5356                        .unwrap_or(None)
5357                })
5358                .collect::<Vec<_>>()
5359        } else {
5360            Vec::new()
5361        };
5362
5363    let has_any_col_names = inputs.iter().any(|input| input.col_names.is_some());
5364    let mut col_names = Vec::new();
5365    if has_any_col_names {
5366        for input in &inputs {
5367            if let Some(names) = &input.col_names {
5368                col_names.extend(
5369                    (0..input.ncol)
5370                        .map(|idx| names.get(idx).cloned().unwrap_or(None))
5371                        .collect::<Vec<_>>(),
5372                );
5373            } else {
5374                col_names.extend(std::iter::repeat_n(None, input.ncol));
5375            }
5376        }
5377    }
5378
5379    let mut rv = RVector::from(Vector::Double(result.into()));
5380    rv.set_attr(
5381        "class".to_string(),
5382        RValue::vec(Vector::Character(
5383            vec![Some("matrix".to_string()), Some("array".to_string())].into(),
5384        )),
5385    );
5386    rv.set_attr(
5387        "dim".to_string(),
5388        RValue::vec(Vector::Integer(
5389            vec![
5390                Some(i64::try_from(max_nrow)?),
5391                Some(i64::try_from(total_ncol)?),
5392            ]
5393            .into(),
5394        )),
5395    );
5396    if let Some(dimnames) = bind_dimnames_value(row_names, col_names) {
5397        rv.set_attr("dimnames".to_string(), dimnames);
5398    }
5399    Ok(RValue::Vector(rv))
5400}
5401
5402/// Column-bind data frames.
5403fn cbind_data_frames(args: &[RValue]) -> Result<RValue, RError> {
5404    let mut output_columns: Vec<(Option<String>, RValue)> = Vec::new();
5405    let mut target_nrow: Option<usize> = None;
5406
5407    for arg in args {
5408        match arg {
5409            RValue::List(list) if has_class(arg, "data.frame") => {
5410                let nrow = dataframes::df_nrow(list);
5411                if let Some(expected) = target_nrow {
5412                    if nrow != expected {
5413                        return Err(RError::new(
5414                            RErrorKind::Argument,
5415                            format!(
5416                                "cbind() arguments have different row counts: {} vs {}",
5417                                expected, nrow
5418                            ),
5419                        ));
5420                    }
5421                } else {
5422                    target_nrow = Some(nrow);
5423                }
5424                for (name, val) in &list.values {
5425                    output_columns.push((name.clone(), val.clone()));
5426                }
5427            }
5428            RValue::Vector(rv) => {
5429                let len = rv.inner.len();
5430                if let Some(expected) = target_nrow {
5431                    if len != expected && len != 1 {
5432                        return Err(RError::new(
5433                            RErrorKind::Argument,
5434                            format!(
5435                                "cbind() arguments have different row counts: {} vs {}",
5436                                expected, len
5437                            ),
5438                        ));
5439                    }
5440                } else {
5441                    target_nrow = Some(len);
5442                }
5443                // Use names attr as column name if available
5444                let col_name = rv
5445                    .get_attr("names")
5446                    .and_then(|v| v.as_vector()?.as_character_scalar());
5447                output_columns.push((col_name, RValue::Vector(rv.clone())));
5448            }
5449            RValue::Null => continue,
5450            _ => {
5451                return Err(RError::new(
5452                    RErrorKind::Argument,
5453                    "cbind() requires data frames or vectors".to_string(),
5454                ))
5455            }
5456        }
5457    }
5458
5459    let nrow = target_nrow.unwrap_or(0);
5460    dataframes::build_data_frame(output_columns, nrow)
5461}
5462
5463/// Get a single attribute of an object.
5464///
5465/// @param x object to inspect
5466/// @param which name of the attribute to retrieve
5467/// @return the attribute value, or NULL if not set
5468#[builtin(min_args = 2)]
5469fn builtin_attr(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5470    let which = args
5471        .get(1)
5472        .and_then(|v| v.as_vector())
5473        .and_then(|v| v.as_character_scalar())
5474        .ok_or_else(|| {
5475            RError::new(
5476                RErrorKind::Argument,
5477                "'which' must be a character string".to_string(),
5478            )
5479        })?;
5480    match args.first() {
5481        Some(RValue::Vector(rv)) => Ok(rv.get_attr(&which).cloned().unwrap_or(RValue::Null)),
5482        Some(RValue::List(l)) => Ok(l.get_attr(&which).cloned().unwrap_or(RValue::Null)),
5483        Some(RValue::Language(lang)) => Ok(lang.get_attr(&which).cloned().unwrap_or(RValue::Null)),
5484        _ => Ok(RValue::Null),
5485    }
5486}
5487
5488/// Set a single attribute on an object.
5489///
5490/// @param x object to modify
5491/// @param which name of the attribute to set
5492/// @param value new attribute value, or NULL to remove it
5493/// @return the modified object
5494#[builtin(name = "attr<-", min_args = 3)]
5495fn builtin_attr_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5496    let which = args
5497        .get(1)
5498        .and_then(|v| v.as_vector())
5499        .and_then(|v| v.as_character_scalar())
5500        .ok_or_else(|| {
5501            RError::new(
5502                RErrorKind::Argument,
5503                "'which' must be a character string".to_string(),
5504            )
5505        })?;
5506    let value = args.get(2).cloned().unwrap_or(RValue::Null);
5507    match args.first() {
5508        Some(RValue::Vector(rv)) => {
5509            let mut rv = rv.clone();
5510            if value.is_null() {
5511                rv.attrs.as_mut().map(|a| a.shift_remove(&which));
5512            } else {
5513                rv.set_attr(which, value);
5514            }
5515            Ok(RValue::Vector(rv))
5516        }
5517        Some(RValue::List(l)) => {
5518            let mut l = l.clone();
5519            if value.is_null() {
5520                l.attrs.as_mut().map(|a| a.shift_remove(&which));
5521            } else {
5522                l.set_attr(which, value);
5523            }
5524            Ok(RValue::List(l))
5525        }
5526        Some(RValue::Language(lang)) => {
5527            let mut lang = lang.clone();
5528            if value.is_null() {
5529                lang.attrs.as_mut().map(|a| a.shift_remove(&which));
5530            } else {
5531                lang.set_attr(which, value);
5532            }
5533            Ok(RValue::Language(lang))
5534        }
5535        other => Ok(other.cloned().unwrap_or(RValue::Null)),
5536    }
5537}
5538
5539/// Get all attributes of an object as a named list.
5540///
5541/// @param x object to inspect
5542/// @return named list of attributes, or NULL if none
5543#[builtin(min_args = 1)]
5544fn builtin_attributes(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5545    let attrs = match args.first() {
5546        Some(RValue::Vector(rv)) => rv.attrs.as_deref(),
5547        Some(RValue::List(l)) => l.attrs.as_deref(),
5548        Some(RValue::Language(lang)) => lang.attrs.as_deref(),
5549        _ => None,
5550    };
5551    match attrs {
5552        Some(a) if !a.is_empty() => {
5553            let values: Vec<(Option<String>, RValue)> = a
5554                .iter()
5555                .map(|(k, v)| (Some(k.clone()), v.clone()))
5556                .collect();
5557            Ok(RValue::List(RList::new(values)))
5558        }
5559        _ => Ok(RValue::Null),
5560    }
5561}
5562
5563/// Set attributes on an object in a single call.
5564///
5565/// Named arguments become attributes on the object. The special names
5566/// ".Names" and "names" set the names attribute.
5567///
5568/// @param .Data object to modify
5569/// @param ... name=value pairs to set as attributes
5570/// @return the modified object
5571#[builtin(min_args = 1)]
5572fn builtin_structure(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
5573    let base = args.first().cloned().unwrap_or(RValue::Null);
5574    if named.is_empty() {
5575        return Ok(base);
5576    }
5577    match base {
5578        RValue::List(mut l) => {
5579            for (name, value) in named {
5580                if name == ".Names" || name == "names" {
5581                    if let RValue::Vector(rv) = value {
5582                        if let Vector::Character(names) = &rv.inner {
5583                            for (i, n) in names.iter().enumerate() {
5584                                if i < l.values.len() {
5585                                    l.values[i].0 = n.clone();
5586                                }
5587                            }
5588                        }
5589                    }
5590                } else {
5591                    l.set_attr(name.clone(), value.clone());
5592                }
5593            }
5594            Ok(RValue::List(l))
5595        }
5596        RValue::Vector(mut rv) => {
5597            for (name, value) in named {
5598                if name == ".Names" || name == "names" {
5599                    rv.set_attr("names".to_string(), value.clone());
5600                } else {
5601                    rv.set_attr(name.clone(), value.clone());
5602                }
5603            }
5604            Ok(RValue::Vector(rv))
5605        }
5606        RValue::Language(mut lang) => {
5607            for (name, value) in named {
5608                lang.set_attr(name.clone(), value.clone());
5609            }
5610            Ok(RValue::Language(lang))
5611        }
5612        other => Ok(other),
5613    }
5614}
5615
5616/// Test if an object inherits from one or more classes.
5617///
5618/// Checks the object's class vector for any matches with the given class names.
5619///
5620/// @param x object to test
5621/// @param what character vector of class names to check
5622/// @return logical scalar
5623#[builtin(min_args = 2)]
5624fn builtin_inherits(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5625    let what = args
5626        .get(1)
5627        .and_then(|v| v.as_vector())
5628        .map(|v| v.to_characters())
5629        .unwrap_or_default();
5630
5631    let classes = match args.first() {
5632        Some(RValue::List(l)) => {
5633            if let Some(RValue::Vector(rv)) = l.get_attr("class") {
5634                if let Vector::Character(cls) = &rv.inner {
5635                    cls.iter().filter_map(|s| s.clone()).collect::<Vec<_>>()
5636                } else {
5637                    vec!["list".to_string()]
5638                }
5639            } else {
5640                vec!["list".to_string()]
5641            }
5642        }
5643        Some(RValue::Vector(rv)) => {
5644            if let Some(cls) = rv.class() {
5645                cls
5646            } else {
5647                match &rv.inner {
5648                    Vector::Raw(_) => vec!["raw".to_string()],
5649                    Vector::Logical(_) => vec!["logical".to_string()],
5650                    Vector::Integer(_) => vec!["integer".to_string()],
5651                    Vector::Double(_) => vec!["numeric".to_string()],
5652                    Vector::Complex(_) => vec!["complex".to_string()],
5653                    Vector::Character(_) => vec!["character".to_string()],
5654                }
5655            }
5656        }
5657        Some(RValue::Function(_)) => vec!["function".to_string()],
5658        Some(RValue::Language(lang)) => lang.class().unwrap_or_default(),
5659        _ => vec![],
5660    };
5661
5662    let result = what
5663        .iter()
5664        .any(|w| w.as_ref().is_some_and(|w| classes.iter().any(|c| c == w)));
5665    Ok(RValue::vec(Vector::Logical(vec![Some(result)].into())))
5666}
5667
5668/// Extract integer dim values from a dim attribute
5669pub(crate) fn get_dim_ints(dim_attr: Option<&RValue>) -> Option<Vec<Option<i64>>> {
5670    match dim_attr {
5671        Some(RValue::Vector(rv)) => match &rv.inner {
5672            Vector::Integer(dims) => Some(dims.iter_opt().collect()),
5673            _ => None,
5674        },
5675        _ => None,
5676    }
5677}
5678
5679pub(crate) fn has_class(val: &RValue, class_name: &str) -> bool {
5680    let class_attr = match val {
5681        RValue::Vector(rv) => rv.get_attr("class"),
5682        RValue::List(l) => l.get_attr("class"),
5683        RValue::Language(lang) => lang.get_attr("class"),
5684        _ => None,
5685    };
5686    if let Some(RValue::Vector(rv)) = class_attr {
5687        if let Vector::Character(cls) = &rv.inner {
5688            return cls.iter().any(|c| c.as_deref() == Some(class_name));
5689        }
5690    }
5691    false
5692}
5693
5694// `environment()` is an interpreter builtin in interp.rs (needs BuiltinContext for no-arg case)
5695
5696/// Create a new environment.
5697///
5698/// @param hash logical; if TRUE, use a hashed environment (always TRUE in miniR since we use HashMap)
5699/// @param parent parent environment (or NULL for an empty environment)
5700/// @param size integer; initial size hint (ignored — HashMap handles resizing)
5701/// @return new environment
5702#[builtin(name = "new.env")]
5703fn builtin_new_env(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
5704    // R signature: new.env(hash = TRUE, parent = parent.frame(), size = 29L)
5705    // Positional order: hash, parent, size
5706    // We accept all three but only parent affects behavior.
5707
5708    let named_hash = named.iter().find(|(n, _)| n == "hash").map(|(_, v)| v);
5709    let named_parent = named.iter().find(|(n, _)| n == "parent").map(|(_, v)| v);
5710    let named_size = named.iter().find(|(n, _)| n == "size").map(|(_, v)| v);
5711
5712    // Resolve positional args (hash, parent, size) skipping those provided as named
5713    let mut positional_iter = args.iter();
5714    let _hash_val = named_hash.or_else(|| positional_iter.next());
5715    // hash is always TRUE in miniR — we use HashMap regardless
5716    let parent_val = named_parent.or_else(|| positional_iter.next());
5717    let _size_val = named_size.or_else(|| positional_iter.next());
5718    // size is a no-op — HashMap resizes dynamically
5719
5720    let parent = parent_val.and_then(|v| {
5721        if let RValue::Environment(e) = v {
5722            Some(e.clone())
5723        } else {
5724            None
5725        }
5726    });
5727    match parent {
5728        Some(p) => Ok(RValue::Environment(Environment::new_child(&p))),
5729        None => Ok(RValue::Environment(Environment::new_empty())),
5730    }
5731}
5732
5733/// Get the name of an environment.
5734///
5735/// @param env environment to query
5736/// @return character scalar (empty string for anonymous environments)
5737#[builtin(name = "environmentName", min_args = 1)]
5738fn builtin_environment_name(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5739    let name = match args.first() {
5740        Some(RValue::Environment(e)) => e.name().unwrap_or_default(),
5741        _ => String::new(),
5742    };
5743    Ok(RValue::vec(Vector::Character(vec![Some(name)].into())))
5744}
5745
5746/// Get the parent (enclosing) environment of an environment.
5747///
5748/// @param env environment to query
5749/// @return the parent environment, or NULL for the empty environment
5750#[builtin(name = "parent.env", min_args = 1)]
5751fn builtin_parent_env(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5752    match args.first() {
5753        Some(RValue::Environment(e)) => match e.parent() {
5754            Some(p) => Ok(RValue::Environment(p)),
5755            None => Ok(RValue::Null),
5756        },
5757        _ => Err(RError::new(
5758            RErrorKind::Argument,
5759            "not an environment".to_string(),
5760        )),
5761    }
5762}
5763
5764/// Set the parent (enclosing) environment.
5765///
5766/// This is a replacement function: `parent.env(e) <- value` sets the parent
5767/// of environment `e` to `value`.
5768///
5769/// @param env environment whose parent to change
5770/// @param value new parent environment
5771/// @return the modified environment (invisibly)
5772#[builtin(name = "parent.env<-", min_args = 2)]
5773fn builtin_parent_env_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5774    let env = match args.first() {
5775        Some(RValue::Environment(e)) => e.clone(),
5776        _ => {
5777            return Err(RError::new(
5778                RErrorKind::Argument,
5779                "not an environment".to_string(),
5780            ))
5781        }
5782    };
5783    let new_parent = match args.get(1) {
5784        Some(RValue::Environment(p)) => Some(p.clone()),
5785        Some(RValue::Null) => None,
5786        _ => {
5787            return Err(RError::new(
5788                RErrorKind::Argument,
5789                "'value' must be an environment or NULL".to_string(),
5790            ))
5791        }
5792    };
5793    env.set_parent(new_parent);
5794    Ok(RValue::Environment(env))
5795}
5796
5797/// Assert that all arguments are TRUE, stopping with an error otherwise.
5798///
5799/// Checks each argument in order. If any element is FALSE or NA,
5800/// raises an error identifying the failing argument.
5801///
5802/// @param ... logical conditions that must all be TRUE
5803/// @return NULL (invisibly) if all conditions pass
5804#[builtin]
5805fn builtin_stopifnot(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5806    for (i, arg) in args.iter().enumerate() {
5807        match arg {
5808            RValue::Vector(rv) if matches!(rv.inner, Vector::Logical(_)) => {
5809                let Vector::Logical(v) = &rv.inner else {
5810                    unreachable!()
5811                };
5812                for (j, val) in v.iter().enumerate() {
5813                    match val {
5814                        Some(true) => {}
5815                        Some(false) => {
5816                            return Err(RError::other(format!(
5817                                "not all are TRUE (element {} of argument {})",
5818                                j + 1,
5819                                i + 1
5820                            )));
5821                        }
5822                        None => {
5823                            return Err(RError::other(format!(
5824                                "missing value where TRUE/FALSE needed (argument {})",
5825                                i + 1
5826                            )));
5827                        }
5828                    }
5829                }
5830            }
5831            RValue::Vector(v) => {
5832                if let Some(b) = v.as_logical_scalar() {
5833                    if !b {
5834                        return Err(RError::other(format!("argument {} is not TRUE", i + 1)));
5835                    }
5836                }
5837            }
5838            _ => {
5839                return Err(RError::other(format!(
5840                    "argument {} is not a logical value",
5841                    i + 1
5842                )));
5843            }
5844        }
5845    }
5846    Ok(RValue::Null)
5847}
5848
5849/// Remove the class attribute from an object.
5850///
5851/// @param x object to unclass
5852/// @return the object with its class attribute removed
5853#[builtin(min_args = 1)]
5854fn builtin_unclass(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
5855    match args.first() {
5856        Some(RValue::Vector(rv)) => {
5857            let mut rv = rv.clone();
5858            rv.attrs.as_mut().map(|a| a.shift_remove("class"));
5859            Ok(RValue::Vector(rv))
5860        }
5861        Some(RValue::List(l)) => {
5862            let mut l = l.clone();
5863            l.attrs.as_mut().map(|a| a.shift_remove("class"));
5864            Ok(RValue::List(l))
5865        }
5866        Some(RValue::Language(lang)) => {
5867            let mut lang = lang.clone();
5868            lang.attrs.as_mut().map(|a| a.shift_remove("class"));
5869            Ok(RValue::Language(lang))
5870        }
5871        other => Ok(other.cloned().unwrap_or(RValue::Null)),
5872    }
5873}
5874
5875/// Match an argument to a set of candidate values.
5876///
5877/// Performs exact and then partial matching against the choices vector.
5878/// When called without explicit `choices`, looks up the calling function's
5879/// formals to determine valid choices (R's standard behavior for
5880/// `match.arg(arg)` inside a function with `arg = c("a","b","c")`).
5881///
5882/// @param arg character scalar (or vector if several.ok=TRUE) to match
5883/// @param choices character vector of allowed values
5884/// @param several.ok logical; if TRUE, allow arg to have length > 1
5885/// @return the matched value(s)
5886#[builtin(name = "match.arg", min_args = 1)]
5887fn builtin_match_arg(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
5888    let arg = args.first().cloned().unwrap_or(RValue::Null);
5889    let choices = args
5890        .get(1)
5891        .or_else(|| named.iter().find(|(n, _)| n == "choices").map(|(_, v)| v));
5892
5893    let several_ok = args
5894        .get(2)
5895        .or_else(|| {
5896            named
5897                .iter()
5898                .find(|(n, _)| n == "several.ok")
5899                .map(|(_, v)| v)
5900        })
5901        .and_then(|v| match v {
5902            RValue::Vector(rv) => rv.as_logical_scalar(),
5903            _ => None,
5904        })
5905        .unwrap_or(false);
5906
5907    let choices_vec = match choices {
5908        Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Character(_)) => {
5909            let Vector::Character(v) = &rv.inner else {
5910                unreachable!()
5911            };
5912            v.iter().filter_map(|s| s.clone()).collect::<Vec<_>>()
5913        }
5914        Some(RValue::Null) | None => {
5915            // No explicit choices provided — use the arg value as both the
5916            // choices and the indicator. In R, match.arg(arg) without choices
5917            // uses the formal default; when the user doesn't override the
5918            // default, arg IS the full choices vector. If arg is a character
5919            // vector with length > 1, treat it as the choices and return the
5920            // first element (the default). If it's length 1, accept it as-is.
5921            match &arg {
5922                RValue::Vector(rv) if matches!(rv.inner, Vector::Character(_)) => {
5923                    let Vector::Character(v) = &rv.inner else {
5924                        unreachable!()
5925                    };
5926                    let strings: Vec<String> =
5927                        v.iter().filter_map(|s| s.clone()).collect::<Vec<_>>();
5928                    if strings.len() > 1 && !several_ok {
5929                        // User didn't override the default — return first choice
5930                        return Ok(RValue::vec(Vector::Character(
5931                            vec![Some(strings[0].clone())].into(),
5932                        )));
5933                    } else if strings.len() == 1 {
5934                        // User provided a single value — accept it
5935                        return Ok(arg);
5936                    } else if strings.is_empty() {
5937                        return Ok(arg);
5938                    }
5939                    // several.ok = TRUE with multi-element arg
5940                    return Ok(arg);
5941                }
5942                RValue::Null => {
5943                    // NULL arg with no choices — can't do anything
5944                    return Ok(RValue::Null);
5945                }
5946                _ => return Ok(arg),
5947            }
5948        }
5949        _ => return Ok(arg),
5950    };
5951
5952    if choices_vec.is_empty() {
5953        return Ok(arg);
5954    }
5955
5956    // Extract the argument strings
5957    let arg_strings: Vec<Option<String>> = match &arg {
5958        RValue::Vector(rv) => match &rv.inner {
5959            Vector::Character(v) => v.iter().cloned().collect(),
5960            _ => vec![None],
5961        },
5962        RValue::Null => vec![],
5963        _ => vec![None],
5964    };
5965
5966    // NULL or empty arg: return first choice (R behavior)
5967    if arg_strings.is_empty() {
5968        return Ok(RValue::vec(Vector::Character(
5969            vec![Some(choices_vec[0].clone())].into(),
5970        )));
5971    }
5972
5973    // If arg equals choices (user didn't override default), return first choice
5974    if !several_ok && arg_strings.len() > 1 && arg_strings.len() == choices_vec.len() {
5975        let all_match = arg_strings
5976            .iter()
5977            .zip(choices_vec.iter())
5978            .all(|(a, c)| a.as_deref() == Some(c.as_str()));
5979        if all_match {
5980            return Ok(RValue::vec(Vector::Character(
5981                vec![Some(choices_vec[0].clone())].into(),
5982            )));
5983        }
5984    }
5985
5986    // If several.ok is FALSE, arg must be length 1
5987    if !several_ok && arg_strings.len() > 1 {
5988        return Err(RError::new(
5989            RErrorKind::Argument,
5990            format!(
5991                "'arg' should be one of {}, not a vector of length {}",
5992                choices_vec.iter().map(|c| format!("'{c}'")).join(", "),
5993                arg_strings.len()
5994            ),
5995        ));
5996    }
5997
5998    // Match each element
5999    let mut matched = Vec::with_capacity(arg_strings.len());
6000    for s_opt in &arg_strings {
6001        let s = match s_opt {
6002            Some(s) => s,
6003            None => {
6004                return Err(RError::new(
6005                    RErrorKind::Argument,
6006                    "'arg' should be one of the choices".to_string(),
6007                ))
6008            }
6009        };
6010
6011        // Exact match first
6012        if choices_vec.contains(s) {
6013            matched.push(Some(s.clone()));
6014            continue;
6015        }
6016        // Partial match
6017        let partial: Vec<&String> = choices_vec
6018            .iter()
6019            .filter(|c| c.starts_with(s.as_str()))
6020            .collect();
6021        match partial.len() {
6022            1 => matched.push(Some(partial[0].clone())),
6023            0 => {
6024                return Err(RError::new(
6025                    RErrorKind::Argument,
6026                    format!(
6027                        "'arg' should be one of {}",
6028                        choices_vec.iter().map(|c| format!("'{c}'")).join(", ")
6029                    ),
6030                ))
6031            }
6032            _ => {
6033                return Err(RError::new(
6034                    RErrorKind::Argument,
6035                    format!(
6036                        "'arg' should be one of {}",
6037                        choices_vec.iter().map(|c| format!("'{c}'")).join(", ")
6038                    ),
6039                ))
6040            }
6041        }
6042    }
6043
6044    Ok(RValue::vec(Vector::Character(matched.into())))
6045}
6046
6047/// Terminate the R session.
6048///
6049/// Also aliased as `quit`.
6050#[builtin(names = ["quit"])]
6051fn builtin_q(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6052    std::process::exit(0);
6053}
6054
6055// === Metaprogramming builtins ===
6056
6057use crate::parser::ast::Expr;
6058
6059/// `formals(fn)` — return the formal parameter list of a function as a named list.
6060///
6061/// For closures, returns param names with defaults. For trait-based builtins
6062/// with `@param` docs, returns the parameter names. Otherwise returns NULL.
6063#[builtin(min_args = 1)]
6064fn builtin_formals(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6065    match args.first() {
6066        Some(RValue::Function(RFunction::Closure { params, .. })) => {
6067            if params.is_empty() {
6068                return Ok(RValue::Null);
6069            }
6070            let entries: Vec<(Option<String>, RValue)> = params
6071                .iter()
6072                .map(|p| {
6073                    let name = if p.is_dots {
6074                        "...".to_string()
6075                    } else {
6076                        p.name.clone()
6077                    };
6078                    let value = match &p.default {
6079                        Some(expr) => RValue::Language(Language::new(expr.clone())),
6080                        None => RValue::Null,
6081                    };
6082                    (Some(name), value)
6083                })
6084                .collect();
6085            Ok(RValue::List(RList::new(entries)))
6086        }
6087        Some(RValue::Function(RFunction::Builtin { name, .. })) => {
6088            if let Some(descriptor) = find_builtin(name) {
6089                let param_names = extract_param_names_from_doc(descriptor.doc);
6090                if !param_names.is_empty() {
6091                    let entries: Vec<(Option<String>, RValue)> = param_names
6092                        .into_iter()
6093                        .map(|n| (Some(n), RValue::Null))
6094                        .collect();
6095                    return Ok(RValue::List(RList::new(entries)));
6096                }
6097            }
6098            Ok(RValue::Null)
6099        }
6100        _ => Err(RError::new(
6101            RErrorKind::Argument,
6102            "'fn' is not a function — formals() requires a function argument".to_string(),
6103        )),
6104    }
6105}
6106
6107/// `body(fn)` — return the body of a function as a Language object.
6108/// For closures, returns the body expression. For builtins, returns NULL.
6109#[builtin(min_args = 1)]
6110fn builtin_body(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6111    match args.first() {
6112        Some(RValue::Function(RFunction::Closure { body, .. })) => {
6113            Ok(RValue::Language(Language::new(body.clone())))
6114        }
6115        Some(RValue::Function(RFunction::Builtin { .. })) => Ok(RValue::Null),
6116        _ => Err(RError::new(
6117            RErrorKind::Argument,
6118            "'fn' is not a function — body() requires a function argument".to_string(),
6119        )),
6120    }
6121}
6122
6123/// `body<-(fn, value)` — set the body of a closure.
6124///
6125/// @param fn function whose body to replace
6126/// @param value new body expression
6127/// @return modified function
6128/// @namespace base
6129#[builtin(name = "body<-", min_args = 2)]
6130fn builtin_body_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6131    match args.first() {
6132        Some(RValue::Function(RFunction::Closure { params, env, .. })) => {
6133            let new_body = match args.get(1) {
6134                Some(RValue::Language(lang)) => (*lang.inner).clone(),
6135                Some(RValue::Null) => crate::parser::ast::Expr::Null,
6136                _ => crate::parser::ast::Expr::Null,
6137            };
6138            Ok(RValue::Function(RFunction::Closure {
6139                params: params.clone(),
6140                body: new_body,
6141                env: env.clone(),
6142            }))
6143        }
6144        _ => Err(RError::new(
6145            RErrorKind::Argument,
6146            "body<- requires a function".to_string(),
6147        )),
6148    }
6149}
6150
6151/// `formals<-(fn, value)` — set the formals of a closure.
6152///
6153/// @param fn function whose formals to replace
6154/// @param value new formals as a pairlist/list
6155/// @return modified function
6156/// @namespace base
6157#[builtin(name = "formals<-", min_args = 2)]
6158fn builtin_formals_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6159    match args.first() {
6160        Some(RValue::Function(RFunction::Closure { body, env, .. })) => {
6161            let new_params = match args.get(1) {
6162                Some(RValue::List(list)) => list
6163                    .values
6164                    .iter()
6165                    .map(|(name, val)| {
6166                        let param_name = name.clone().unwrap_or_default();
6167                        let default = if matches!(val, RValue::Null) {
6168                            None
6169                        } else {
6170                            // Convert RValue back to Expr for default — simplified
6171                            None
6172                        };
6173                        crate::parser::ast::Param {
6174                            name: param_name,
6175                            default,
6176                            is_dots: name.as_deref() == Some("..."),
6177                        }
6178                    })
6179                    .collect(),
6180                _ => vec![],
6181            };
6182            Ok(RValue::Function(RFunction::Closure {
6183                params: new_params,
6184                body: body.clone(),
6185                env: env.clone(),
6186            }))
6187        }
6188        _ => Err(RError::new(
6189            RErrorKind::Argument,
6190            "formals<- requires a function".to_string(),
6191        )),
6192    }
6193}
6194
6195/// `environment<-(fn, value)` — set the environment of a closure.
6196///
6197/// @param fun function whose environment to set
6198/// @param value new environment
6199/// @return modified function
6200/// @namespace base
6201#[builtin(name = "environment<-", min_args = 2)]
6202fn builtin_environment_set(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6203    match args.first() {
6204        Some(RValue::Function(RFunction::Closure { params, body, .. })) => {
6205            let new_env = match args.get(1) {
6206                Some(RValue::Environment(e)) => e.clone(),
6207                _ => {
6208                    return Err(RError::new(
6209                        RErrorKind::Argument,
6210                        "replacement environment must be an environment".to_string(),
6211                    ))
6212                }
6213            };
6214            Ok(RValue::Function(RFunction::Closure {
6215                params: params.clone(),
6216                body: body.clone(),
6217                env: new_env,
6218            }))
6219        }
6220        _ => Err(RError::new(
6221            RErrorKind::Argument,
6222            "environment<- requires a function".to_string(),
6223        )),
6224    }
6225}
6226
6227/// `args(fn)` — return the formals of a function (simplified: same as formals).
6228/// In GNU R, args() returns a function with the same formals but NULL body.
6229/// We simplify to just returning formals, which covers all practical uses.
6230#[builtin(min_args = 1)]
6231fn builtin_args(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
6232    // args(f) returns a function with the same formals as f but NULL body.
6233    // This is used by memoise: formals(args(f)) to get the formals.
6234    let f = args
6235        .first()
6236        .ok_or_else(|| RError::new(RErrorKind::Argument, "args() requires a function argument"))?;
6237    match f {
6238        RValue::Function(func) => match func {
6239            RFunction::Closure { params, env, .. } => Ok(RValue::Function(RFunction::Closure {
6240                params: params.clone(),
6241                body: Expr::Null,
6242                env: env.clone(),
6243            })),
6244            RFunction::Builtin { formals, .. } => {
6245                let params: Vec<Param> = formals
6246                    .iter()
6247                    .map(|&s| Param {
6248                        name: s.to_string(),
6249                        default: None,
6250                        is_dots: s == "...",
6251                    })
6252                    .collect();
6253                Ok(RValue::Function(RFunction::Closure {
6254                    params,
6255                    body: Expr::Null,
6256                    env: crate::interpreter::environment::Environment::new_empty(),
6257                }))
6258            }
6259        },
6260        RValue::Null => Ok(RValue::Null),
6261        _ => Ok(RValue::Null), // args() on non-function returns NULL in R
6262    }
6263}
6264
6265/// `call(name, ...)` — construct an unevaluated function call expression.
6266/// `call("f", 1, 2)` returns the language object `f(1, 2)`.
6267#[builtin(name = "call", min_args = 1)]
6268fn builtin_call(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
6269    let func_name = args
6270        .first()
6271        .and_then(|v| v.as_vector()?.as_character_scalar())
6272        .ok_or_else(|| {
6273            RError::new(
6274                RErrorKind::Argument,
6275                "first argument must be a character string naming the function to call".to_string(),
6276            )
6277        })?;
6278
6279    // Build Arg list from remaining positional args + named args
6280    let mut call_args: Vec<crate::parser::ast::Arg> = Vec::new();
6281
6282    for val in args.iter().skip(1) {
6283        call_args.push(Arg {
6284            name: None,
6285            value: Some(rvalue_to_expr(val)),
6286        });
6287    }
6288
6289    for (name, val) in named {
6290        call_args.push(Arg {
6291            name: Some(name.clone()),
6292            value: Some(rvalue_to_expr(val)),
6293        });
6294    }
6295
6296    let expr = Expr::Call {
6297        func: Box::new(Expr::Symbol(func_name)),
6298        args: call_args,
6299        span: None,
6300    };
6301
6302    Ok(RValue::Language(Language::new(expr)))
6303}
6304
6305/// `UseMethod()` is intercepted directly by the evaluator so it can unwind the
6306/// current generic frame instead of returning like an ordinary builtin.
6307#[builtin(name = "UseMethod", min_args = 1)]
6308fn builtin_use_method(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6309    Err(RError::other(
6310        "internal error: UseMethod() should be intercepted during evaluation",
6311    ))
6312}
6313
6314// `expression()` is a pre-eval builtin — see builtins/pre_eval.rs
6315
6316// `Recall(...)` is an interpreter builtin — see builtins/interp.rs
6317
6318// region: locale, gc, debugging stubs
6319
6320/// Return locale-specific numeric formatting conventions.
6321///
6322/// Returns a named character vector with C locale defaults (miniR does not
6323/// support locale switching).
6324///
6325/// @return named character vector of locale conventions
6326#[builtin(name = "Sys.localeconv")]
6327fn builtin_sys_localeconv(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6328    let names = vec![
6329        "decimal_point",
6330        "thousands_sep",
6331        "grouping",
6332        "int_curr_symbol",
6333        "currency_symbol",
6334        "mon_decimal_point",
6335        "mon_thousands_sep",
6336        "mon_grouping",
6337        "positive_sign",
6338        "negative_sign",
6339        "int_frac_digits",
6340        "frac_digits",
6341        "p_cs_precedes",
6342        "p_sep_by_space",
6343        "n_cs_precedes",
6344        "n_sep_by_space",
6345        "p_sign_posn",
6346        "n_sign_posn",
6347    ];
6348    let values: Vec<Option<String>> = vec![
6349        Some(".".to_string()),   // decimal_point
6350        Some(String::new()),     // thousands_sep
6351        Some(String::new()),     // grouping
6352        Some(String::new()),     // int_curr_symbol
6353        Some(String::new()),     // currency_symbol
6354        Some(String::new()),     // mon_decimal_point
6355        Some(String::new()),     // mon_thousands_sep
6356        Some(String::new()),     // mon_grouping
6357        Some(String::new()),     // positive_sign
6358        Some(String::new()),     // negative_sign
6359        Some("127".to_string()), // int_frac_digits (CHAR_MAX)
6360        Some("127".to_string()), // frac_digits
6361        Some("127".to_string()), // p_cs_precedes
6362        Some("127".to_string()), // p_sep_by_space
6363        Some("127".to_string()), // n_cs_precedes
6364        Some("127".to_string()), // n_sep_by_space
6365        Some("127".to_string()), // p_sign_posn
6366        Some("127".to_string()), // n_sign_posn
6367    ];
6368    let mut rv = RVector::from(Vector::Character(values.into()));
6369    rv.set_attr(
6370        "names".to_string(),
6371        RValue::vec(Vector::Character(
6372            names
6373                .into_iter()
6374                .map(|s| Some(s.to_string()))
6375                .collect::<Vec<_>>()
6376                .into(),
6377        )),
6378    );
6379    Ok(RValue::Vector(rv))
6380}
6381
6382/// Trigger garbage collection (stub).
6383///
6384/// miniR uses Rust's ownership model for memory management, so there is no
6385/// garbage collector to invoke. Returns invisible NULL.
6386///
6387/// @param verbose logical; if TRUE, print GC info (ignored)
6388/// @param reset logical; if TRUE, reset max memory stats (ignored)
6389/// @param full logical; if TRUE, do a full collection (ignored)
6390/// @return invisible NULL
6391#[builtin]
6392fn builtin_gc(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6393    // Return a minimal named double matrix matching R's gc() output shape:
6394    // 2 rows (Ncells, Vcells) x 6 cols (used, gc trigger, max used, ...)
6395    // For simplicity, return zeros.
6396    Ok(RValue::Null)
6397}
6398
6399/// Set or query verbose GC reporting (stub).
6400///
6401/// miniR does not have a garbage collector. Always returns FALSE.
6402///
6403/// @param verbose logical (ignored)
6404/// @return previous setting (always FALSE)
6405#[builtin]
6406fn builtin_gcinfo(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6407    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
6408}
6409
6410/// Print the call stack of the last error.
6411///
6412/// Displays the call stack that was active when the last error occurred,
6413/// numbered from the bottom (outermost) to the top (innermost) of the stack.
6414///
6415/// @param x number of calls to display (ignored)
6416/// @return the traceback as a character vector, invisibly
6417#[interpreter_builtin(names = ["traceBack"])]
6418fn interp_traceback(
6419    _args: &[RValue],
6420    _named: &[(String, RValue)],
6421    context: &BuiltinContext,
6422) -> Result<RValue, RError> {
6423    context.with_interpreter(|interp| {
6424        let tb = interp.last_traceback.borrow();
6425        if tb.is_empty() {
6426            context.write("No traceback available\n");
6427            interp.set_invisible();
6428            return Ok(RValue::Null);
6429        }
6430        let mut lines = Vec::with_capacity(tb.len());
6431        for (i, entry) in tb.iter().enumerate().rev() {
6432            let call_str = match &entry.call {
6433                Some(expr) => crate::interpreter::value::deparse_expr(expr),
6434                None => "<anonymous>".to_string(),
6435            };
6436            let line = format!("{}: {}", i + 1, call_str);
6437            context.write(&format!("{}\n", line));
6438            lines.push(Some(line));
6439        }
6440        interp.set_invisible();
6441        Ok(RValue::vec(Vector::Character(lines.into())))
6442    })
6443}
6444
6445/// Set a function for debug-mode single-stepping (stub).
6446///
6447/// miniR does not support interactive debugging. Prints a message.
6448///
6449/// @param fun function to debug
6450/// @return invisible NULL
6451#[interpreter_builtin]
6452fn interp_debug(
6453    _args: &[RValue],
6454    _named: &[(String, RValue)],
6455    context: &BuiltinContext,
6456) -> Result<RValue, RError> {
6457    context
6458        .write_err("debug() is not supported in miniR — interactive debugging is not available.\n");
6459    Ok(RValue::Null)
6460}
6461
6462/// Remove debug-mode single-stepping from a function (stub).
6463///
6464/// @param fun function to undebug
6465/// @return invisible NULL
6466#[builtin]
6467fn builtin_undebug(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6468    Ok(RValue::Null)
6469}
6470
6471/// Query whether a function has debug-mode enabled (stub).
6472///
6473/// @param fun function to query
6474/// @return always FALSE
6475#[builtin(name = "isdebugged")]
6476fn builtin_isdebugged(_args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
6477    Ok(RValue::vec(Vector::Logical(vec![Some(false)].into())))
6478}
6479
6480/// Enter the browser for interactive debugging (stub).
6481///
6482/// miniR does not support interactive debugging. Prints a message.
6483///
6484/// @return invisible NULL
6485#[interpreter_builtin]
6486fn interp_browser(
6487    _args: &[RValue],
6488    _named: &[(String, RValue)],
6489    context: &BuiltinContext,
6490) -> Result<RValue, RError> {
6491    context.write_err(
6492        "browser() is not supported in miniR — interactive debugging is not available.\n",
6493    );
6494    Ok(RValue::Null)
6495}
6496
6497// endregion
6498
6499/// Convert an RValue back to an AST expression (for call/expression construction).
6500fn rvalue_to_expr(val: &RValue) -> Expr {
6501    match val {
6502        RValue::Language(expr) => *expr.inner.clone(),
6503        RValue::Null => Expr::Null,
6504        RValue::Vector(rv) => match &rv.inner {
6505            Vector::Double(d) if d.len() == 1 => match d.get_opt(0) {
6506                Some(v) if v.is_infinite() && v > 0.0 => Expr::Inf,
6507                Some(v) if v.is_nan() => Expr::NaN,
6508                Some(v) => Expr::Double(v),
6509                None => Expr::Na(crate::parser::ast::NaType::Real),
6510            },
6511            Vector::Integer(i) if i.len() == 1 => match i.get_opt(0) {
6512                Some(v) => Expr::Integer(v),
6513                None => Expr::Na(crate::parser::ast::NaType::Integer),
6514            },
6515            Vector::Logical(l) if l.len() == 1 => match l[0] {
6516                Some(v) => Expr::Bool(v),
6517                None => Expr::Na(crate::parser::ast::NaType::Logical),
6518            },
6519            Vector::Character(c) if c.len() == 1 => match &c[0] {
6520                Some(v) => Expr::String(v.clone()),
6521                None => Expr::Na(crate::parser::ast::NaType::Character),
6522            },
6523            _ => Expr::Symbol(format!("{}", val)),
6524        },
6525        _ => Expr::Symbol(format!("{}", val)),
6526    }
6527}