Skip to main content

jaq_core/
exn.rs

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