jaq_core/
exn.rs

1//! Exceptions and errors.
2
3use crate::RcList;
4use alloc::{boxed::Box, string::String, string::ToString, vec::Vec};
5use core::fmt::{self, Display};
6
7/// Exception.
8///
9/// This is either an error or control flow data internal to jaq.
10/// Users should only be able to observe errors.
11///
12/// Use [`crate::val::unwrap_valr`] to convert a [`crate::val::ValX`] to an error.
13#[derive(Clone, Debug)]
14pub struct Exn<V>(pub(crate) Inner<V>);
15
16#[derive(Clone, Debug)]
17pub(crate) enum Inner<V> {
18    Err(Box<Error<V>>),
19    /// Tail-recursive call.
20    ///
21    /// This is used internally to execute tail-recursive filters.
22    /// If this can be observed by users, then this is a bug.
23    TailCall(Box<(crate::compile::TermId, crate::filter::Vars<V>, CallInput<V>)>),
24    Break(usize),
25}
26
27#[derive(Clone, Debug)]
28pub(crate) enum CallInput<V> {
29    Run(V),
30    Paths((V, RcList<V>)),
31}
32
33impl<V> CallInput<V> {
34    pub fn unwrap_run(self) -> V {
35        match self {
36            Self::Run(v) => v,
37            _ => panic!(),
38        }
39    }
40
41    pub fn unwrap_paths(self) -> (V, RcList<V>) {
42        match self {
43            Self::Paths(vp) => vp,
44            _ => panic!(),
45        }
46    }
47}
48
49impl<V> Exn<V> {
50    /// If the exception is an error, yield it, else yield the exception.
51    pub(crate) fn get_err(self) -> Result<Error<V>, Self> {
52        match self.0 {
53            Inner::Err(e) => Ok(*e),
54            _ => Err(self),
55        }
56    }
57}
58
59impl<V> From<Error<V>> for Exn<V> {
60    fn from(e: Error<V>) -> Self {
61        Exn(Inner::Err(Box::new(e)))
62    }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
66enum Part<V, S = &'static str> {
67    Val(V),
68    Str(S),
69}
70
71/// Error that occurred during filter execution.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct Error<V>(Part<V, Vec<Part<V>>>);
74
75impl<V> Error<V> {
76    /// Create a new error from a value.
77    pub fn new(v: V) -> Self {
78        Self(Part::Val(v))
79    }
80
81    /// Create a path expression error.
82    pub fn path_expr(v: V) -> Self {
83        Self(Part::Str(Vec::from([
84            Part::Str("invalid path expression with input "),
85            Part::Val(v),
86        ])))
87    }
88
89    /// Create a type error.
90    pub fn typ(v: V, typ: &'static str) -> Self {
91        use Part::{Str, Val};
92        [Str("cannot use "), Val(v), Str(" as "), Str(typ)]
93            .into_iter()
94            .collect()
95    }
96
97    /// Create a math error.
98    pub fn math(l: V, op: crate::ops::Math, r: V) -> Self {
99        use Part::{Str, Val};
100        [
101            Str("cannot calculate "),
102            Val(l),
103            Str(" "),
104            Str(op.as_str()),
105            Str(" "),
106            Val(r),
107        ]
108        .into_iter()
109        .collect()
110    }
111
112    /// Create an indexing error.
113    pub fn index(l: V, r: V) -> Self {
114        use Part::{Str, Val};
115        [Str("cannot index "), Val(l), Str(" with "), Val(r)]
116            .into_iter()
117            .collect()
118    }
119}
120
121impl<V: From<String>> Error<V> {
122    /// Build an error from something that can be converted to a string.
123    pub fn str(s: impl ToString) -> Self {
124        Self(Part::Val(V::from(s.to_string())))
125    }
126}
127
128impl<V> FromIterator<Part<V>> for Error<V> {
129    fn from_iter<T: IntoIterator<Item = Part<V>>>(iter: T) -> Self {
130        Self(Part::Str(iter.into_iter().collect()))
131    }
132}
133
134impl<V: From<String> + Display> Error<V> {
135    /// Convert the error into a value to be used by `catch` filters.
136    pub fn into_val(self) -> V {
137        if let Part::Val(v) = self.0 {
138            v
139        } else {
140            V::from(self.to_string())
141        }
142    }
143}
144
145impl<V: Display> Display for Error<V> {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        match &self.0 {
148            Part::Val(v) => v.fmt(f),
149            Part::Str(parts) => parts.iter().try_for_each(|part| match part {
150                Part::Val(v) => v.fmt(f),
151                Part::Str(s) => s.fmt(f),
152            }),
153        }
154    }
155}