Skip to main content

r/interpreter/value/
traits.rs

1//! Builtin trait system: `Builtin`, `FromArgs`, `CoerceArg`, and argument decoding helpers.
2//!
3//! R arguments have three states that matter: missing (not provided), NULL
4//! (explicitly passed), or a value. This module models all three through the
5//! [`RArg<T>`] enum and provides derive-friendly coercion traits.
6
7use super::vector::Vector;
8use super::{RError, RErrorKind, RValue};
9use crate::interpreter::environment::Environment;
10use crate::interpreter::BuiltinContext;
11
12// region: RArg<T> — three-state R argument
13
14/// The three states of an R function argument.
15///
16/// In R, `missing(x)` returns TRUE when the caller didn't provide `x` at all,
17/// while `is.null(x)` returns TRUE when the caller explicitly passed NULL.
18/// These are semantically different and many builtins branch on the distinction.
19///
20/// Use `RArg<T>` as a field type in `#[derive(FromArgs)]` structs when you need
21/// the full three-way distinction. For simpler cases, use plain `T` (required)
22/// or `Option<T>` (missing → None, NULL coerces or errors).
23#[derive(Debug, Clone)]
24pub enum RArg<T> {
25    /// Caller did not provide this argument. `missing(x)` would return TRUE.
26    Missing,
27    /// Caller explicitly passed NULL.
28    Null,
29    /// Caller provided a value (possibly NA — that's inside T).
30    Value(T),
31}
32
33impl<T> RArg<T> {
34    pub fn is_missing(&self) -> bool {
35        matches!(self, RArg::Missing)
36    }
37
38    pub fn is_null(&self) -> bool {
39        matches!(self, RArg::Null)
40    }
41
42    pub fn value(&self) -> Option<&T> {
43        match self {
44            RArg::Value(v) => Some(v),
45            _ => None,
46        }
47    }
48
49    pub fn into_value(self) -> Option<T> {
50        match self {
51            RArg::Value(v) => Some(v),
52            _ => None,
53        }
54    }
55
56    /// Collapse both Missing and Null to None, Value to Some.
57    pub fn optional(self) -> Option<T> {
58        self.into_value()
59    }
60
61    /// Use a default when missing, but propagate NULL distinctly.
62    pub fn or_default(self, default: T) -> Self {
63        match self {
64            RArg::Missing => RArg::Value(default),
65            other => other,
66        }
67    }
68
69    /// Unwrap to value, using `default` for both Missing and Null.
70    pub fn unwrap_or(self, default: T) -> T {
71        match self {
72            RArg::Value(v) => v,
73            _ => default,
74        }
75    }
76
77    /// Unwrap to value, using `f()` for both Missing and Null.
78    pub fn unwrap_or_else(self, f: impl FnOnce() -> T) -> T {
79        match self {
80            RArg::Value(v) => v,
81            _ => f(),
82        }
83    }
84
85    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> RArg<U> {
86        match self {
87            RArg::Missing => RArg::Missing,
88            RArg::Null => RArg::Null,
89            RArg::Value(v) => RArg::Value(f(v)),
90        }
91    }
92}
93
94// endregion
95
96// region: Dots — variadic argument capture
97
98/// Captures all remaining positional arguments (`...` in R).
99///
100/// Use as a field type in `#[derive(FromArgs)]` structs for variadic builtins
101/// like `c()`, `paste()`, `cat()`, `list()`, etc. Named arguments that match
102/// other struct fields are consumed first; everything else goes into Dots.
103#[derive(Debug, Clone, Default)]
104pub struct Dots(pub Vec<RValue>);
105
106impl Dots {
107    pub fn len(&self) -> usize {
108        self.0.len()
109    }
110
111    pub fn is_empty(&self) -> bool {
112        self.0.is_empty()
113    }
114
115    pub fn iter(&self) -> std::slice::Iter<'_, RValue> {
116        self.0.iter()
117    }
118}
119
120impl IntoIterator for Dots {
121    type Item = RValue;
122    type IntoIter = std::vec::IntoIter<RValue>;
123    fn into_iter(self) -> Self::IntoIter {
124        self.0.into_iter()
125    }
126}
127
128impl<'a> IntoIterator for &'a Dots {
129    type Item = &'a RValue;
130    type IntoIter = std::slice::Iter<'a, RValue>;
131    fn into_iter(self) -> Self::IntoIter {
132        self.0.iter()
133    }
134}
135
136// endregion
137
138// region: Traits
139
140/// Metadata for a trait-based builtin, generated by `#[derive(FromArgs)]`.
141pub struct BuiltinInfo {
142    pub name: &'static str,
143    pub aliases: &'static [&'static str],
144    pub min_args: usize,
145    pub max_args: Option<usize>,
146    pub doc: &'static str,
147    /// Parameter names (from struct field names), for formals()/args().
148    pub params: &'static [&'static str],
149}
150
151/// Decode R call arguments into a typed struct.
152///
153/// Implemented by `#[derive(FromArgs)]` on structs whose fields represent
154/// R function parameters. Field names become R parameter names, field types
155/// determine coercion, and `#[default(...)]` marks optional parameters.
156///
157/// Field type determines behavior:
158/// - `T` — required, error if missing
159/// - `Option<T>` — optional, `None` if missing
160/// - `RArg<T>` — three-way: Missing / Null / Value
161/// - `Dots` — captures remaining positional args
162pub trait FromArgs: Sized {
163    /// Decode positional and named R arguments into Self.
164    fn from_args(args: &[RValue], named: &[(String, RValue)]) -> Result<Self, RError>;
165
166    /// Static metadata: parameter count, names, docs.
167    fn info() -> &'static BuiltinInfo;
168}
169
170/// A trait-based builtin function.
171///
172/// Implementors define their arguments as a struct with `#[derive(FromArgs)]`,
173/// then implement `Builtin::call` to execute the function body.
174pub trait Builtin: FromArgs {
175    /// Execute the builtin with decoded arguments.
176    fn call(self, ctx: &BuiltinContext) -> Result<RValue, RError>;
177}
178
179// endregion
180
181// region: Argument lookup helpers
182
183/// Find an argument by name (with partial matching) or positional index.
184pub fn find_arg<'a>(
185    args: &'a [RValue],
186    named: &'a [(String, RValue)],
187    param_name: &str,
188    positional_index: usize,
189) -> Option<&'a RValue> {
190    if let Some(v) = find_named_arg(named, param_name) {
191        return Some(v);
192    }
193    // Positional fallback
194    args.get(positional_index)
195}
196
197/// Find an argument by name only (exact match first, then unique partial match).
198/// Used by the `FromArgs` derive macro with a runtime positional counter.
199pub fn find_named_arg<'a>(named: &'a [(String, RValue)], param_name: &str) -> Option<&'a RValue> {
200    // Exact name match first
201    for (name, val) in named {
202        if name == param_name {
203            return Some(val);
204        }
205    }
206    // Partial name match
207    let candidates: Vec<_> = named
208        .iter()
209        .filter(|(name, _)| param_name.starts_with(name.as_str()))
210        .collect();
211    if candidates.len() == 1 {
212        return Some(&candidates[0].1);
213    }
214    None
215}
216
217// endregion
218
219// region: CoerceArg trait and impls
220
221/// Coerce an RValue to a Rust type. Implemented for common R parameter types.
222pub trait CoerceArg: Sized {
223    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError>;
224}
225
226impl CoerceArg for f64 {
227    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
228        val.as_vector()
229            .and_then(|v| v.as_double_scalar())
230            .ok_or_else(|| {
231                RError::new(
232                    RErrorKind::Argument,
233                    format!("'{}' must be numeric", param_name),
234                )
235            })
236    }
237}
238
239impl CoerceArg for i64 {
240    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
241        val.as_vector()
242            .and_then(|v| v.as_integer_scalar())
243            .ok_or_else(|| {
244                RError::new(
245                    RErrorKind::Argument,
246                    format!("'{}' must be an integer", param_name),
247                )
248            })
249    }
250}
251
252impl CoerceArg for usize {
253    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
254        let i = val
255            .as_vector()
256            .and_then(|v| v.as_integer_scalar())
257            .ok_or_else(|| {
258                RError::new(
259                    RErrorKind::Argument,
260                    format!("'{}' must be a non-negative integer", param_name),
261                )
262            })?;
263        usize::try_from(i).map_err(|_| {
264            RError::new(
265                RErrorKind::Argument,
266                format!("'{}' must be a non-negative integer, got {}", param_name, i),
267            )
268        })
269    }
270}
271
272impl CoerceArg for bool {
273    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
274        val.as_vector()
275            .and_then(|v| v.as_logical_scalar())
276            .ok_or_else(|| {
277                RError::new(
278                    RErrorKind::Argument,
279                    format!("'{}' must be logical", param_name),
280                )
281            })
282    }
283}
284
285impl CoerceArg for String {
286    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
287        val.as_vector()
288            .and_then(|v| v.as_character_scalar())
289            .ok_or_else(|| {
290                RError::new(
291                    RErrorKind::Argument,
292                    format!("'{}' must be a character string", param_name),
293                )
294            })
295    }
296}
297
298impl CoerceArg for RValue {
299    fn coerce(val: &RValue, _param_name: &str) -> Result<Self, RError> {
300        Ok(val.clone())
301    }
302}
303
304impl CoerceArg for Vector {
305    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
306        val.as_vector().cloned().ok_or_else(|| {
307            RError::new(
308                RErrorKind::Argument,
309                format!("'{}' must be a vector", param_name),
310            )
311        })
312    }
313}
314
315impl CoerceArg for Environment {
316    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
317        match val {
318            RValue::Environment(env) => Ok(env.clone()),
319            _ => Err(RError::new(
320                RErrorKind::Argument,
321                format!("'{}' must be an environment", param_name),
322            )),
323        }
324    }
325}
326
327/// `Option<T>` coerces the value if present, but returns `None` for NULL.
328/// Missing args are handled by the derive (never reach CoerceArg).
329impl<T: CoerceArg> CoerceArg for Option<T> {
330    fn coerce(val: &RValue, param_name: &str) -> Result<Self, RError> {
331        if matches!(val, RValue::Null) {
332            return Ok(None);
333        }
334        T::coerce(val, param_name).map(Some)
335    }
336}
337
338/// Helper called by `FromArgs` derive-generated code.
339pub fn coerce_arg<T: CoerceArg>(val: &RValue, param_name: &str) -> Result<T, RError> {
340    T::coerce(val, param_name)
341}
342
343/// Helper called by `FromArgs` derive-generated code for `RArg<T>` fields.
344/// Produces the three-way Missing/Null/Value from a raw `Option<&RValue>`.
345pub fn coerce_arg_three_way<T: CoerceArg>(
346    val: Option<&RValue>,
347    param_name: &str,
348) -> Result<RArg<T>, RError> {
349    match val {
350        None => Ok(RArg::Missing),
351        Some(RValue::Null) => Ok(RArg::Null),
352        Some(v) => T::coerce(v, param_name).map(RArg::Value),
353    }
354}
355
356// endregion