Skip to main content

r/interpreter/value/
error.rs

1//! R error types, control flow signals, and condition constructors.
2
3use std::fmt;
4use std::num::TryFromIntError;
5use std::sync::Arc;
6
7use super::{RList, RValue, Vector};
8
9/// The kind of R condition (error, warning, or message).
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ConditionKind {
12    Error,
13    Warning,
14    Message,
15}
16
17/// The R-facing error category — determines how R's condition system classifies the error.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RErrorKind {
20    Type,
21    Argument,
22    Name,
23    Index,
24    Parse,
25    Interrupt,
26    Other,
27}
28
29/// R runtime error — wraps any module error with an R-facing category and preserves
30/// the full error chain via `Arc<dyn Error>`.
31///
32/// Construct via `RError::new()`, `RError::from_source()`, or `From<T: Error>` impls.
33/// Module errors (IoError, MathError, etc.) convert automatically via the blanket impl.
34#[derive(Debug, Clone)]
35pub enum RError {
36    /// Standard error with kind, message, and optional source chain.
37    Standard {
38        kind: RErrorKind,
39        message: String,
40        #[allow(dead_code)]
41        source: Option<Arc<dyn std::error::Error + Send + Sync>>,
42    },
43    /// R condition signal — carries a condition object (list with class attribute).
44    /// This is distinct from standard errors because it carries an RValue, not a
45    /// std::error::Error.
46    Condition {
47        condition: RValue,
48        kind: ConditionKind,
49    },
50}
51
52/// Control flow signals — not errors, but propagated via Result for convenience
53#[derive(Debug, Clone)]
54pub enum RSignal {
55    Return(RValue),
56    Break,
57    Next,
58}
59
60/// Combined error/signal type for the evaluator.
61/// Builtins return `Result<RValue, RError>`, the evaluator returns `Result<RValue, RFlow>`.
62#[derive(Debug, Clone)]
63pub enum RFlow {
64    Error(RError),
65    Signal(RSignal),
66}
67
68// region: RError impls
69
70impl RError {
71    /// Create an error with a kind and message (no source chain).
72    pub fn new(kind: RErrorKind, message: impl Into<String>) -> Self {
73        RError::Standard {
74            kind,
75            message: message.into(),
76            source: None,
77        }
78    }
79
80    /// Create an error that wraps a source error, using its Display as the message.
81    pub fn from_source(
82        kind: RErrorKind,
83        source: impl std::error::Error + Send + Sync + 'static,
84    ) -> Self {
85        let message = format!("{}", source);
86        RError::Standard {
87            kind,
88            message,
89            source: Some(Arc::new(source)),
90        }
91    }
92
93    /// Convenience: create an Other error with just a message.
94    pub fn other(message: impl Into<String>) -> Self {
95        RError::new(RErrorKind::Other, message)
96    }
97
98    /// Create an interrupt error (Ctrl+C during computation).
99    pub fn interrupt() -> Self {
100        RError::new(RErrorKind::Interrupt, "Ctrl+C: computation interrupted")
101    }
102
103    /// Returns true if this error represents a user interrupt (Ctrl+C).
104    pub fn is_interrupt(&self) -> bool {
105        matches!(
106            self,
107            RError::Standard {
108                kind: RErrorKind::Interrupt,
109                ..
110            }
111        )
112    }
113
114    /// Return the error kind (or None for Condition).
115    #[allow(dead_code)]
116    pub fn kind(&self) -> Option<RErrorKind> {
117        match self {
118            RError::Standard { kind, .. } => Some(*kind),
119            RError::Condition { .. } => None,
120        }
121    }
122
123    /// Extract the error message string from any RError variant.
124    #[allow(dead_code)]
125    pub fn message(&self) -> String {
126        match self {
127            RError::Standard { message, .. } => message.clone(),
128            RError::Condition { condition, .. } => {
129                if let RValue::List(list) = condition {
130                    for (name, val) in &list.values {
131                        if name.as_deref() == Some("message") {
132                            if let RValue::Vector(rv) = val {
133                                if let Some(s) = rv.as_character_scalar() {
134                                    return s;
135                                }
136                            }
137                        }
138                    }
139                }
140                format!("{}", self)
141            }
142        }
143    }
144
145    /// Get the source error, if any.
146    #[allow(dead_code)]
147    pub fn source(&self) -> Option<&(dyn std::error::Error + Send + Sync)> {
148        match self {
149            RError::Standard { source, .. } => source.as_deref(),
150            RError::Condition { .. } => None,
151        }
152    }
153}
154
155impl fmt::Display for RError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            RError::Standard { kind, message, .. } => {
159                let prefix = match kind {
160                    RErrorKind::Type => "Error",
161                    RErrorKind::Argument => "Error in argument",
162                    RErrorKind::Name => "Error",
163                    RErrorKind::Index => "Error in indexing",
164                    RErrorKind::Parse => "Error in parse",
165                    RErrorKind::Interrupt => return write!(f, "Interrupted"),
166                    RErrorKind::Other => "Error",
167                };
168                // Name errors have a special format
169                if *kind == RErrorKind::Name {
170                    write!(f, "Error: object '{}' not found", message)
171                } else {
172                    write!(f, "{}: {}", prefix, message)
173                }
174            }
175            RError::Condition { condition, .. } => {
176                if let RValue::List(list) = condition {
177                    for (name, val) in &list.values {
178                        if name.as_deref() == Some("message") {
179                            if let RValue::Vector(rv) = val {
180                                if let Some(s) = rv.as_character_scalar() {
181                                    return write!(f, "Error: {}", s);
182                                }
183                            }
184                        }
185                    }
186                }
187                write!(f, "Error: <condition>")
188            }
189        }
190    }
191}
192
193// endregion
194
195// region: RFlow impls
196
197impl From<RError> for RFlow {
198    fn from(e: RError) -> Self {
199        RFlow::Error(e)
200    }
201}
202
203impl From<RSignal> for RFlow {
204    fn from(s: RSignal) -> Self {
205        RFlow::Signal(s)
206    }
207}
208
209impl From<RFlow> for RError {
210    /// Convert back from RFlow to RError. Signals become Other errors (they shouldn't
211    /// normally reach builtin boundaries, but if they do we don't lose information).
212    fn from(f: RFlow) -> Self {
213        match f {
214            RFlow::Error(e) => e,
215            RFlow::Signal(s) => RError::other(format!("{}", s)),
216        }
217    }
218}
219
220impl From<TryFromIntError> for RFlow {
221    fn from(e: TryFromIntError) -> Self {
222        RFlow::Error(RError::from_source(RErrorKind::Type, e))
223    }
224}
225
226impl From<TryFromIntError> for RError {
227    fn from(e: TryFromIntError) -> Self {
228        RError::from_source(RErrorKind::Type, e)
229    }
230}
231
232impl fmt::Display for RFlow {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        match self {
235            RFlow::Error(e) => write!(f, "{}", e),
236            RFlow::Signal(s) => write!(f, "{}", s),
237        }
238    }
239}
240
241impl fmt::Display for RSignal {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        match self {
244            RSignal::Return(_) => write!(f, "no function to return from"),
245            RSignal::Break => write!(f, "no loop for break/next, jumping to top level"),
246            RSignal::Next => write!(f, "no loop for break/next, jumping to top level"),
247        }
248    }
249}
250
251// endregion
252
253// region: Condition constructors
254
255/// Create an R condition object (a list with message, call, and class attributes).
256pub fn make_condition(message: &str, classes: &[&str]) -> RValue {
257    let mut list = RList::new(vec![
258        (
259            Some("message".to_string()),
260            RValue::vec(Vector::Character(vec![Some(message.to_string())].into())),
261        ),
262        (Some("call".to_string()), RValue::Null),
263    ]);
264    let class_vec: Vec<Option<String>> = classes.iter().map(|s| Some(s.to_string())).collect();
265    list.set_attr(
266        "class".to_string(),
267        RValue::vec(Vector::Character(class_vec.into())),
268    );
269    RValue::List(list)
270}
271
272/// Create an R condition object with an explicit call value.
273pub fn make_condition_with_call(message: &str, call: RValue, classes: &[&str]) -> RValue {
274    let mut list = RList::new(vec![
275        (
276            Some("message".to_string()),
277            RValue::vec(Vector::Character(vec![Some(message.to_string())].into())),
278        ),
279        (Some("call".to_string()), call),
280    ]);
281    let class_vec: Vec<Option<String>> = classes.iter().map(|s| Some(s.to_string())).collect();
282    list.set_attr(
283        "class".to_string(),
284        RValue::vec(Vector::Character(class_vec.into())),
285    );
286    RValue::List(list)
287}
288
289/// Get the class vector from an RValue (if it has a class attribute).
290pub fn get_class(val: &RValue) -> Vec<String> {
291    let attrs = match val {
292        RValue::Vector(rv) => rv.attrs.as_ref(),
293        RValue::List(list) => list.attrs.as_ref(),
294        RValue::Language(lang) => lang.attrs.as_ref(),
295        _ => None,
296    };
297    match attrs.and_then(|a| a.get("class")) {
298        Some(RValue::Vector(rv)) => match &rv.inner {
299            Vector::Character(v) => v.iter().filter_map(|s| s.clone()).collect(),
300            _ => vec![],
301        },
302        _ => vec![],
303    }
304}
305
306// endregion