Skip to main content

r/interpreter/builtins/
types.rs

1//! Type-checking builtins: `is.null`, `is.na`, `is.numeric`, etc.
2//!
3//! Each function tests whether an R value belongs to a particular type
4//! or satisfies a type predicate, returning a logical scalar or vector.
5
6use crate::interpreter::value::*;
7use crate::parser::ast::Expr;
8use minir_macros::builtin;
9
10use super::{get_dim_ints, has_class};
11
12/// Test if an object is NULL.
13///
14/// @param x object to test
15/// @return logical scalar
16#[builtin(min_args = 1)]
17fn builtin_is_null(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
18    let r = matches!(args.first(), Some(RValue::Null));
19    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
20}
21
22/// Test if an object is an ordered factor.
23///
24/// @param x object to test
25/// @return logical scalar
26#[builtin(min_args = 1)]
27fn builtin_is_ordered(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
28    let r = args.first().is_some_and(|v| has_class(v, "ordered"));
29    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
30}
31
32/// Test if an object is a call (language object).
33///
34/// In R, `is.call(x)` returns TRUE for unevaluated function call expressions
35/// (including binary/unary ops, control flow, etc.) but NOT for symbols.
36/// `is.call(quote(f(x)))` -> TRUE, `is.call(quote(x))` -> FALSE.
37///
38/// @param x object to test
39/// @return logical scalar
40#[builtin(min_args = 1)]
41fn builtin_is_call(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
42    let r = matches!(
43        args.first(),
44        Some(RValue::Language(lang)) if !matches!(*lang.inner, Expr::Symbol(_))
45    );
46    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
47}
48
49/// Test if an object is a symbol (name).
50///
51/// In R, `is.symbol(x)` / `is.name(x)` returns TRUE for name objects.
52/// In miniR, Language wrapping a bare `Expr::Symbol` is a symbol.
53///
54/// @param x object to test
55/// @return logical scalar
56#[builtin(min_args = 1, names = ["is.name"])]
57fn builtin_is_symbol(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
58    let r = matches!(
59        args.first(),
60        Some(RValue::Language(lang)) if matches!(*lang.inner, Expr::Symbol(_))
61    );
62    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
63}
64
65/// Test if an object is an expression object.
66///
67/// In R, expression objects are created by `expression()`. In miniR, these are
68/// represented as lists with class "expression".
69///
70/// @param x object to test
71/// @return logical scalar
72#[builtin(min_args = 1)]
73fn builtin_is_expression(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
74    let r = args.first().is_some_and(|v| has_class(v, "expression"));
75    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
76}
77
78/// Test if an object is a pairlist.
79///
80/// In GNU R, pairlists are a linked-list type used internally. miniR has no
81/// separate pairlist type. NULL is R's empty pairlist, so `is.pairlist(NULL)`
82/// returns TRUE. Regular lists are NOT pairlists (`is.pairlist(list())` -> FALSE).
83///
84/// @param x object to test
85/// @return logical scalar
86#[builtin(min_args = 1)]
87fn builtin_is_pairlist(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
88    let r = matches!(args.first(), Some(RValue::Null));
89    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
90}
91
92/// Test if an object is an environment.
93///
94/// @param x object to test
95/// @return logical scalar
96#[builtin(name = "is.environment", min_args = 1)]
97fn builtin_is_environment(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
98    let r = matches!(args.first(), Some(RValue::Environment(_)));
99    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
100}
101
102/// Test if an object is a language object (unevaluated expression).
103///
104/// @param x object to test
105/// @return logical scalar
106#[builtin(name = "is.language", min_args = 1)]
107fn builtin_is_language(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
108    let r = matches!(args.first(), Some(RValue::Language(_)));
109    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
110}
111
112/// Test which elements are NA (missing values).
113///
114/// For doubles, NaN is also considered NA.
115///
116/// @param x vector to test
117/// @return logical vector of the same length
118#[builtin(min_args = 1)]
119fn builtin_is_na(args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
120    match args.first() {
121        Some(RValue::Vector(v)) => {
122            let result: Vec<Option<bool>> = match &v.inner {
123                Vector::Raw(vals) => vals.iter().map(|_| Some(false)).collect(),
124                Vector::Logical(vals) => vals.iter().map(|x| Some(x.is_none())).collect(),
125                Vector::Integer(vals) => vals.iter().map(|x| Some(x.is_none())).collect(),
126                Vector::Double(vals) => vals
127                    .iter()
128                    .map(|x| Some(x.is_none() || x.map(|f| f.is_nan()).unwrap_or(false)))
129                    .collect(),
130                Vector::Complex(vals) => vals.iter().map(|x| Some(x.is_none())).collect(),
131                Vector::Character(vals) => vals.iter().map(|x| Some(x.is_none())).collect(),
132            };
133            Ok(RValue::vec(Vector::Logical(result.into())))
134        }
135        _ => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
136    }
137}
138
139/// Test if an object is numeric (integer or double).
140///
141/// Returns FALSE for factors even though they use integer storage,
142/// matching GNU R's behavior.
143///
144/// @param x object to test
145/// @return logical scalar
146#[builtin(min_args = 1)]
147fn builtin_is_numeric(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
148    let r = match args.first() {
149        Some(val @ RValue::Vector(rv))
150            if matches!(rv.inner, Vector::Double(_) | Vector::Integer(_)) =>
151        {
152            // Factors are not numeric despite integer storage
153            !has_class(val, "factor")
154        }
155        _ => false,
156    };
157    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
158}
159
160/// Test if an object is a character vector.
161///
162/// @param x object to test
163/// @return logical scalar
164#[builtin(min_args = 1)]
165fn builtin_is_character(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
166    let r = matches!(args.first(), Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Character(_)));
167    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
168}
169
170/// Test if an object is a logical vector.
171///
172/// @param x object to test
173/// @return logical scalar
174#[builtin(min_args = 1)]
175fn builtin_is_logical(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
176    let r =
177        matches!(args.first(), Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Logical(_)));
178    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
179}
180
181/// Test if an object is an integer vector.
182///
183/// @param x object to test
184/// @return logical scalar
185#[builtin(min_args = 1)]
186fn builtin_is_integer(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
187    let r =
188        matches!(args.first(), Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Integer(_)));
189    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
190}
191
192/// Test if an object is a double (real-valued) vector.
193///
194/// @param x object to test
195/// @return logical scalar
196#[builtin(min_args = 1)]
197fn builtin_is_double(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
198    let r =
199        matches!(args.first(), Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Double(_)));
200    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
201}
202
203/// Test if an object is a function (closure or builtin).
204///
205/// @param x object to test
206/// @return logical scalar
207#[builtin(min_args = 1)]
208fn builtin_is_function(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
209    let r = matches!(args.first(), Some(RValue::Function(_)));
210    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
211}
212
213/// Test if an object is a primitive (builtin) function.
214///
215/// Returns TRUE only for builtin functions, not user-defined closures.
216///
217/// @param x object to test
218/// @return logical scalar
219#[builtin(min_args = 1)]
220fn builtin_is_primitive(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
221    let r = matches!(
222        args.first(),
223        Some(RValue::Function(RFunction::Builtin { .. }))
224    );
225    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
226}
227
228/// Test if an object is a vector with no attributes other than names.
229///
230/// Returns TRUE for atomic vectors and lists that have no attributes
231/// beyond "names".
232///
233/// @param x object to test
234/// @return logical scalar
235#[builtin(min_args = 1)]
236fn builtin_is_vector(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
237    // R's is.vector returns TRUE only if the vector has no attributes other than "names"
238    let r = match args.first() {
239        Some(RValue::Vector(rv)) => match &rv.attrs {
240            None => true,
241            Some(attrs) => attrs.keys().all(|k| k == "names"),
242        },
243        Some(RValue::List(l)) => match &l.attrs {
244            None => true,
245            Some(attrs) => attrs.keys().all(|k| k == "names"),
246        },
247        _ => false,
248    };
249    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
250}
251
252/// Test if an object is a list.
253///
254/// @param x object to test
255/// @return logical scalar
256#[builtin(min_args = 1)]
257fn builtin_is_list(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
258    let r = matches!(args.first(), Some(RValue::List(_)));
259    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
260}
261
262/// Test if an object is recursive (list, environment, or call/language object).
263///
264/// In R, recursive objects can contain other objects. This includes lists,
265/// environments, and language objects (calls), but NOT symbols or atomic vectors.
266///
267/// @param x object to test
268/// @return logical scalar
269#[builtin(min_args = 1)]
270fn builtin_is_recursive(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
271    let r = match args.first() {
272        Some(RValue::List(_)) | Some(RValue::Environment(_)) => true,
273        // Language objects that are calls (not symbols) are recursive
274        Some(RValue::Language(lang)) => !matches!(*lang.inner, Expr::Symbol(_)),
275        _ => false,
276    };
277    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
278}
279
280/// Test if an object is atomic (vector or NULL).
281///
282/// @param x object to test
283/// @return logical scalar
284#[builtin(min_args = 1)]
285fn builtin_is_atomic(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
286    // R's is.atomic: TRUE for atomic vectors and NULL
287    let r = matches!(args.first(), Some(RValue::Vector(_)) | Some(RValue::Null));
288    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
289}
290
291/// Test which elements are finite (not Inf, -Inf, NaN, or NA).
292///
293/// @param x numeric vector to test
294/// @return logical vector of the same length
295#[builtin(min_args = 1)]
296fn builtin_is_finite(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
297    match args.first() {
298        Some(RValue::Vector(v)) => {
299            let result: Vec<Option<bool>> = match &v.inner {
300                Vector::Double(vals) => vals
301                    .iter()
302                    .map(|x| Some(x.map(|f| f.is_finite()).unwrap_or(false)))
303                    .collect(),
304                // Non-NA integers and logicals are always finite
305                Vector::Integer(vals) => vals.iter().map(|x| Some(x.is_some())).collect(),
306                Vector::Logical(vals) => vals.iter().map(|x| Some(x.is_some())).collect(),
307                _ => {
308                    return Err(RError::new(
309                        RErrorKind::Argument,
310                        "default method not implemented for type".to_string(),
311                    ))
312                }
313            };
314            Ok(RValue::vec(Vector::Logical(result.into())))
315        }
316        _ => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
317    }
318}
319
320/// Test which elements are infinite (Inf or -Inf).
321///
322/// @param x numeric vector to test
323/// @return logical vector of the same length
324#[builtin(min_args = 1)]
325fn builtin_is_infinite(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
326    match args.first() {
327        Some(RValue::Vector(v)) => {
328            let result: Vec<Option<bool>> = match &v.inner {
329                Vector::Double(vals) => vals
330                    .iter()
331                    .map(|x| Some(x.map(|f| f.is_infinite()).unwrap_or(false)))
332                    .collect(),
333                // Integers and logicals are never infinite
334                Vector::Integer(_) | Vector::Logical(_) => {
335                    vec![Some(false); v.inner.len()]
336                }
337                _ => {
338                    return Err(RError::new(
339                        RErrorKind::Argument,
340                        "default method not implemented for type".to_string(),
341                    ))
342                }
343            };
344            Ok(RValue::vec(Vector::Logical(result.into())))
345        }
346        _ => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
347    }
348}
349
350/// Test which elements are NaN (not-a-number).
351///
352/// Unlike `is.na()`, this returns FALSE for NA values that are not NaN.
353///
354/// @param x numeric vector to test
355/// @return logical vector of the same length
356#[builtin(min_args = 1)]
357fn builtin_is_nan(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
358    match args.first() {
359        Some(RValue::Vector(v)) => {
360            let result: Vec<Option<bool>> = match &v.inner {
361                Vector::Double(vals) => vals
362                    .iter()
363                    .map(|x| Some(x.map(|f| f.is_nan()).unwrap_or(false)))
364                    .collect(),
365                // Integers and logicals are never NaN
366                Vector::Integer(_) | Vector::Logical(_) => {
367                    vec![Some(false); v.inner.len()]
368                }
369                _ => {
370                    return Err(RError::new(
371                        RErrorKind::Argument,
372                        "default method not implemented for type".to_string(),
373                    ))
374                }
375            };
376            Ok(RValue::vec(Vector::Logical(result.into())))
377        }
378        _ => Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
379    }
380}
381
382/// Test if an object is a factor.
383///
384/// @param x object to test
385/// @return logical scalar
386#[builtin(min_args = 1)]
387fn builtin_is_factor(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
388    let r = args.first().is_some_and(|v| has_class(v, "factor"));
389    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
390}
391
392/// Test if an object is a data frame.
393///
394/// @param x object to test
395/// @return logical scalar
396#[builtin(min_args = 1)]
397fn builtin_is_data_frame(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
398    let r = args.first().is_some_and(|v| has_class(v, "data.frame"));
399    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
400}
401
402/// Test if an object is a matrix (has dim attribute of length 2).
403///
404/// @param x object to test
405/// @return logical scalar
406#[builtin(min_args = 1)]
407fn builtin_is_matrix(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
408    let r = args.first().is_some_and(|v| {
409        // Check class attribute
410        if has_class(v, "matrix") {
411            return true;
412        }
413        // A matrix is any object with a dim attribute of length 2
414        let dim_attr = match v {
415            RValue::Vector(rv) => rv.get_attr("dim"),
416            RValue::List(l) => l.get_attr("dim"),
417            _ => None,
418        };
419        get_dim_ints(dim_attr).is_some_and(|d| d.len() == 2)
420    });
421    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
422}
423
424/// Test if an object is an array.
425///
426/// An array is any object with a dim attribute. This includes matrices
427/// (dim of length 2) and higher-dimensional arrays.
428///
429/// @param x object to test
430/// @return logical scalar
431#[builtin(min_args = 1)]
432fn builtin_is_array(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
433    let r = args.first().is_some_and(|v| {
434        // Check class attribute first
435        if has_class(v, "array") || has_class(v, "matrix") {
436            return true;
437        }
438        // An array is any object with a dim attribute
439        let dim_attr = match v {
440            RValue::Vector(rv) => rv.get_attr("dim"),
441            RValue::List(l) => l.get_attr("dim"),
442            _ => None,
443        };
444        get_dim_ints(dim_attr).is_some_and(|d| !d.is_empty())
445    });
446    Ok(RValue::vec(Vector::Logical(vec![Some(r)].into())))
447}
448
449/// Test set membership: is each element of el in table?
450///
451/// Uses numeric comparison for numeric vectors (matching `%in%` behavior),
452/// and string comparison when either side is character.
453///
454/// @param el values to test
455/// @param table values to match against
456/// @return logical vector of the same length as el
457#[builtin(min_args = 2)]
458fn builtin_is_element(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
459    if args.len() < 2 {
460        return Err(RError::new(
461            RErrorKind::Argument,
462            "need 2 arguments".to_string(),
463        ));
464    }
465    let (lv, rv) = match (&args[0], &args[1]) {
466        (RValue::Vector(l), RValue::Vector(r)) => (l, r),
467        _ => return Ok(RValue::vec(Vector::Logical(vec![Some(false)].into()))),
468    };
469
470    // Character comparison when either side is character
471    if matches!(lv.inner, Vector::Character(_)) || matches!(rv.inner, Vector::Character(_)) {
472        let table = rv.to_characters();
473        let vals = lv.to_characters();
474        let result: Vec<Option<bool>> = vals
475            .iter()
476            .map(|xi| {
477                Some(
478                    xi.as_ref()
479                        .is_some_and(|xi| table.iter().any(|t| t.as_ref() == Some(xi))),
480                )
481            })
482            .collect();
483        return Ok(RValue::vec(Vector::Logical(result.into())));
484    }
485
486    // Numeric comparison (handles int/double/logical via doubles)
487    let table = rv.to_doubles();
488    let vals = lv.to_doubles();
489    let result: Vec<Option<bool>> = vals
490        .iter()
491        .map(|x| match x {
492            Some(v) => Some(table.iter().any(|t| match t {
493                Some(t) => (*t == *v) || (t.is_nan() && v.is_nan()),
494                None => false,
495            })),
496            None => Some(table.iter().any(|t| t.is_none())),
497        })
498        .collect();
499    Ok(RValue::vec(Vector::Logical(result.into())))
500}
501
502/// Test if x is a single TRUE value.
503///
504/// Returns TRUE only if x is a length-1 logical vector equal to TRUE (not NA).
505///
506/// @param x object to test
507/// @return logical scalar
508#[builtin(name = "isTRUE", min_args = 1)]
509fn builtin_is_true(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
510    let result = match args.first() {
511        Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Logical(_)) => {
512            let Vector::Logical(v) = &rv.inner else {
513                unreachable!()
514            };
515            v.len() == 1 && v[0] == Some(true)
516        }
517        _ => false,
518    };
519    Ok(RValue::vec(Vector::Logical(vec![Some(result)].into())))
520}
521
522/// Test if x is a single FALSE value.
523///
524/// Returns TRUE only if x is a length-1 logical vector equal to FALSE (not NA).
525///
526/// @param x object to test
527/// @return logical scalar
528#[builtin(name = "isFALSE", min_args = 1)]
529fn builtin_is_false(args: &[RValue], _: &[(String, RValue)]) -> Result<RValue, RError> {
530    let result = match args.first() {
531        Some(RValue::Vector(rv)) if matches!(rv.inner, Vector::Logical(_)) => {
532            let Vector::Logical(v) = &rv.inner else {
533                unreachable!()
534            };
535            v.len() == 1 && v[0] == Some(false)
536        }
537        _ => false,
538    };
539    Ok(RValue::vec(Vector::Logical(vec![Some(result)].into())))
540}