Skip to main content

r/interpreter/value/
vector.rs

1//! Atomic vector types (`Vector` enum) and element-level conversions.
2
3use super::{Character, ComplexVec, Double, Integer, Logical};
4use crate::interpreter::coerce;
5
6/// Atomic vector types in R
7#[derive(Debug, Clone)]
8pub enum Vector {
9    Raw(Vec<u8>),
10    Logical(Logical),
11    Integer(Integer),
12    Double(Double),
13    Complex(ComplexVec),
14    Character(Character),
15}
16
17impl Vector {
18    pub fn len(&self) -> usize {
19        match self {
20            Vector::Raw(v) => v.len(),
21            Vector::Logical(v) => v.len(),
22            Vector::Integer(v) => v.len(),
23            Vector::Double(v) => v.len(),
24            Vector::Complex(v) => v.len(),
25            Vector::Character(v) => v.len(),
26        }
27    }
28
29    #[allow(dead_code)]
30    pub fn is_empty(&self) -> bool {
31        self.len() == 0
32    }
33
34    /// Build a new vector by selecting elements at the given indices (with recycling).
35    /// Preserves the vector type.
36    pub fn select_indices(&self, indices: &[usize]) -> Vector {
37        macro_rules! sel_option {
38            ($vals:expr, $variant:ident) => {{
39                let result: Vec<_> = indices
40                    .iter()
41                    .map(|&i| {
42                        if i < $vals.len() {
43                            $vals[i].clone()
44                        } else {
45                            Default::default()
46                        }
47                    })
48                    .collect();
49                Vector::$variant(result.into())
50            }};
51        }
52
53        match self {
54            Vector::Raw(vals) => {
55                let result: Vec<u8> = indices
56                    .iter()
57                    .map(|&i| if i < vals.len() { vals[i] } else { 0 })
58                    .collect();
59                Vector::Raw(result)
60            }
61            Vector::Double(vals) => Vector::Double(vals.select_indices(indices)),
62            Vector::Integer(vals) => Vector::Integer(vals.select_indices(indices)),
63            Vector::Logical(vals) => sel_option!(vals, Logical),
64            Vector::Complex(vals) => sel_option!(vals, Complex),
65            Vector::Character(vals) => sel_option!(vals, Character),
66        }
67    }
68
69    /// Get the first element as a boolean (for conditions)
70    pub fn as_logical_scalar(&self) -> Option<bool> {
71        match self {
72            Vector::Logical(v) => v.first().copied().flatten(),
73            Vector::Integer(v) => v.first_opt().map(|i| i != 0),
74            Vector::Double(v) => v.first_opt().map(|f| f != 0.0),
75            Vector::Complex(v) => v
76                .first()
77                .copied()
78                .flatten()
79                .map(|c| c.re != 0.0 || c.im != 0.0),
80            Vector::Raw(_) | Vector::Character(_) => None,
81        }
82    }
83
84    /// Get the first element as f64
85    pub fn as_double_scalar(&self) -> Option<f64> {
86        match self {
87            Vector::Double(v) => v.first_opt(),
88            Vector::Integer(v) => v.first_opt().map(coerce::i64_to_f64),
89            Vector::Logical(v) => v
90                .first()
91                .copied()
92                .flatten()
93                .map(|b| if b { 1.0 } else { 0.0 }),
94            Vector::Raw(v) => v.first().map(|&b| f64::from(b)),
95            Vector::Complex(_) => None, // complex cannot coerce to double without losing info
96            Vector::Character(v) => v.first().cloned().flatten().and_then(|s| s.parse().ok()),
97        }
98    }
99
100    /// Get the first element as i64
101    pub fn as_integer_scalar(&self) -> Option<i64> {
102        match self {
103            Vector::Integer(v) => v.first_opt(),
104            Vector::Double(v) => v.first_opt().and_then(|f| coerce::f64_to_i64(f).ok()),
105            Vector::Logical(v) => v.first().copied().flatten().map(|b| if b { 1 } else { 0 }),
106            Vector::Raw(v) => v.first().map(|&b| i64::from(b)),
107            Vector::Complex(_) => None,
108            Vector::Character(v) => v.first().cloned().flatten().and_then(|s| s.parse().ok()),
109        }
110    }
111
112    /// Get the first element as String
113    pub fn as_character_scalar(&self) -> Option<String> {
114        match self {
115            Vector::Character(v) => v.first().cloned().flatten(),
116            Vector::Double(v) => v.first_opt().map(format_r_double),
117            Vector::Integer(v) => v.first_opt().map(|i| i.to_string()),
118            Vector::Logical(v) => v.first().copied().flatten().map(|b| {
119                if b {
120                    "TRUE".to_string()
121                } else {
122                    "FALSE".to_string()
123                }
124            }),
125            Vector::Complex(v) => v.first().copied().flatten().map(format_r_complex),
126            Vector::Raw(v) => v.first().map(|b| format!("{:02x}", b)),
127        }
128    }
129
130    /// Convert entire vector to doubles
131    pub fn to_doubles(&self) -> Vec<Option<f64>> {
132        match self {
133            Vector::Double(v) => v.iter_opt().collect(),
134            Vector::Integer(v) => v.iter_opt().map(|x| x.map(coerce::i64_to_f64)).collect(),
135            Vector::Logical(v) => v
136                .iter()
137                .map(|x| x.map(|b| if b { 1.0 } else { 0.0 }))
138                .collect(),
139            Vector::Raw(v) => v.iter().map(|&b| Some(f64::from(b))).collect(),
140            Vector::Complex(v) => v.iter().map(|x| x.map(|c| c.re)).collect(),
141            Vector::Character(v) => v
142                .iter()
143                .map(|x| x.as_ref().and_then(|s| s.parse().ok()))
144                .collect(),
145        }
146    }
147
148    /// Convert entire vector to integers
149    pub fn to_integers(&self) -> Vec<Option<i64>> {
150        match self {
151            Vector::Integer(v) => v.iter_opt().collect(),
152            Vector::Double(v) => v
153                .iter_opt()
154                .map(|x| x.and_then(|f| coerce::f64_to_i64(f).ok()))
155                .collect(),
156            Vector::Logical(v) => v.iter().map(|x| x.map(|b| if b { 1 } else { 0 })).collect(),
157            Vector::Raw(v) => v.iter().map(|&b| Some(i64::from(b))).collect(),
158            Vector::Complex(v) => v
159                .iter()
160                .map(|x| x.and_then(|c| coerce::f64_to_i64(c.re).ok()))
161                .collect(),
162            Vector::Character(v) => v
163                .iter()
164                .map(|x| x.as_ref().and_then(|s| s.parse().ok()))
165                .collect(),
166        }
167    }
168
169    /// Convert entire vector to characters
170    pub fn to_characters(&self) -> Vec<Option<String>> {
171        match self {
172            Vector::Character(v) => v.0.clone(),
173            Vector::Double(v) => v.iter_opt().map(|x| x.map(format_r_double)).collect(),
174            Vector::Integer(v) => v.iter_opt().map(|x| x.map(|i| i.to_string())).collect(),
175            Vector::Logical(v) => v
176                .iter()
177                .map(|x| {
178                    x.map(|b| {
179                        if b {
180                            "TRUE".to_string()
181                        } else {
182                            "FALSE".to_string()
183                        }
184                    })
185                })
186                .collect(),
187            Vector::Raw(v) => v.iter().map(|b| Some(format!("{:02x}", b))).collect(),
188            Vector::Complex(v) => v.iter().map(|x| x.map(format_r_complex)).collect(),
189        }
190    }
191
192    /// Convert to logicals
193    pub fn to_logicals(&self) -> Vec<Option<bool>> {
194        match self {
195            Vector::Logical(v) => v.0.clone(),
196            Vector::Integer(v) => v.iter_opt().map(|x| x.map(|i| i != 0)).collect(),
197            Vector::Double(v) => v.iter_opt().map(|x| x.map(|f| f != 0.0)).collect(),
198            Vector::Raw(v) => v.iter().map(|&b| Some(b != 0)).collect(),
199            Vector::Complex(v) => v
200                .iter()
201                .map(|x| x.map(|c| c.re != 0.0 || c.im != 0.0))
202                .collect(),
203            Vector::Character(v) => v
204                .iter()
205                .map(|x| {
206                    x.as_ref().and_then(|s| match s.trim() {
207                        "TRUE" | "T" => Some(true),
208                        "FALSE" | "F" => Some(false),
209                        _ => None,
210                    })
211                })
212                .collect(),
213        }
214    }
215
216    /// Convert entire vector to complex
217    pub fn to_complex(&self) -> Vec<Option<num_complex::Complex64>> {
218        match self {
219            Vector::Complex(v) => v.0.clone(),
220            Vector::Double(v) => v
221                .iter_opt()
222                .map(|x| x.map(|f| num_complex::Complex64::new(f, 0.0)))
223                .collect(),
224            Vector::Integer(v) => v
225                .iter_opt()
226                .map(|x| x.map(|i| num_complex::Complex64::new(coerce::i64_to_f64(i), 0.0)))
227                .collect(),
228            Vector::Logical(v) => v
229                .iter()
230                .map(|x| x.map(|b| num_complex::Complex64::new(if b { 1.0 } else { 0.0 }, 0.0)))
231                .collect(),
232            Vector::Raw(v) => v
233                .iter()
234                .map(|&b| Some(num_complex::Complex64::new(f64::from(b), 0.0)))
235                .collect(),
236            Vector::Character(v) => v
237                .iter()
238                .map(|x| {
239                    x.as_ref()
240                        .and_then(|s| s.parse::<f64>().ok())
241                        .map(|f| num_complex::Complex64::new(f, 0.0))
242                })
243                .collect(),
244        }
245    }
246
247    pub fn type_name(&self) -> &'static str {
248        match self {
249            Vector::Raw(_) => "raw",
250            Vector::Logical(_) => "logical",
251            Vector::Integer(_) => "integer",
252            Vector::Double(_) => "double",
253            Vector::Complex(_) => "complex",
254            Vector::Character(_) => "character",
255        }
256    }
257
258    /// Convert entire vector to raw bytes (truncating to 0-255)
259    pub fn to_raw(&self) -> Vec<u8> {
260        match self {
261            Vector::Raw(v) => v.clone(),
262            Vector::Integer(v) => v
263                .iter_opt()
264                .map(|x| x.map(|i| (i & 0xff) as u8).unwrap_or(0))
265                .collect(),
266            Vector::Double(v) => v
267                .iter_opt()
268                .map(|x| x.map(|f| (f as i64 & 0xff) as u8).unwrap_or(0))
269                .collect(),
270            Vector::Logical(v) => v.iter().map(|x| x.map(u8::from).unwrap_or(0)).collect(),
271            Vector::Complex(v) => v
272                .iter()
273                .map(|x| x.map(|c| (c.re as i64 & 0xff) as u8).unwrap_or(0))
274                .collect(),
275            Vector::Character(_) => vec![0; self.len()],
276        }
277    }
278}
279
280/// Format an f64 the way R does (integer-valued doubles without decimal point, Inf, NaN, etc.)
281pub fn format_r_double(f: f64) -> String {
282    if f.is_nan() {
283        "NaN".to_string()
284    } else if f.is_infinite() {
285        if f > 0.0 {
286            "Inf".to_string()
287        } else {
288            "-Inf".to_string()
289        }
290    } else if f == f.floor() && f.abs() < 1e15 {
291        // Safe: we checked f is finite, integer-valued, and within range
292        format!("{}", coerce::f64_to_i64(f).unwrap_or(0))
293    } else {
294        format_finite_double(f)
295    }
296}
297
298/// Format a finite, non-integer f64 to its shortest decimal representation.
299///
300/// When the `fast-format` feature is enabled, uses the zmij crate (Schubfach/yy
301/// algorithm) for significantly faster conversion. Falls back to `format!("{}", f)`
302/// otherwise.
303fn format_finite_double(f: f64) -> String {
304    #[cfg(feature = "fast-format")]
305    {
306        zmij::Buffer::new().format_finite(f).to_owned()
307    }
308    #[cfg(not(feature = "fast-format"))]
309    {
310        format!("{}", f)
311    }
312}
313
314/// Format a complex number the way R does.
315pub fn format_r_complex(c: num_complex::Complex64) -> String {
316    let re = format_r_double(c.re);
317    if c.im >= 0.0 || c.im.is_nan() {
318        format!("{}+{}i", re, format_r_double(c.im))
319    } else {
320        format!("{}{}i", re, format_r_double(c.im))
321    }
322}