Skip to main content

axsys_noun/
convert.rs

1//! Conversions to and from [`Noun`](crate::noun::Noun).
2
3use std::fmt::{self, Display, Formatter};
4
5/// Errors that occur when converting from a noun.
6#[derive(Debug)]
7pub enum Error {
8    /// An atom could not be converted into an unsigned integer.
9    AtomToUint,
10    /// An atom could not be converted into a string.
11    AtomToStr,
12    /// A null atom was expected.
13    ExpectedNull,
14    /// An error specific to the implementing type occurred.
15    ImplType,
16    /// No value exists at a particular axis of a cell.
17    MissingValue,
18    /// Encountered an atom when a cell was expected.
19    UnexpectedAtom,
20    /// Encountered a cell when an atom was expected.
21    UnexpectedCell,
22}
23
24impl Display for Error {
25    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
26        match self {
27            Self::AtomToUint => write!(
28                f,
29                "the atom is too large to fit in the unsigned integer type"
30            ),
31            Self::AtomToStr => write!(f, "the atom is not composed of valid UTF-8 bytes"),
32            Self::ExpectedNull => write!(f, "a null atom was expected"),
33            Self::ImplType => write!(f, "an error specific to the implementing type occurred"),
34            Self::MissingValue => write!(f, "the noun does not have a value at this axis"),
35            Self::UnexpectedAtom => write!(f, "an atom was encountered when a cell was expected"),
36            Self::UnexpectedCell => write!(f, "a cell was encountered when an atom was expected"),
37        }
38    }
39}
40
41/// Converts [`Noun`](crate::Noun)s to and from other complex types.
42///
43/// There are three forms of this macro:
44///
45/// - Convert a [`&Noun`] of the form `[e0 e1 ... eN 0]` (a null-terminated list) to a
46///   [`Vec`]`<$elem_type>`, returning [`Result`]`<`[`Vec`]`<$elem_type>, `[`Error`]`>`.
47///
48///   `$elem_type` must implement [`TryFrom`]`<`[`&Noun`]`>`.
49///
50///   The resulting [`Vec`] does not include the null terminator.
51///
52/// ```
53/// # use axsys_noun::{convert, noun::Noun};
54/// let noun = Noun::null();
55/// let vec = convert!(&noun => Vec<String>).unwrap();
56/// assert!(vec.is_empty());
57/// ```
58///
59/// ```
60/// # use axsys_noun::{atom::Atom, cell::Cell, convert, noun::Noun};
61/// let noun = Noun::from(Cell::from([
62///     Atom::from("hello"),
63///     Atom::from("world"),
64///     Atom::null(),
65/// ]));
66/// let vec = convert!(&noun => Vec<String>).unwrap();
67/// assert_eq!(vec, vec!["hello", "world"]);
68/// ```
69///
70/// - Convert a [`&Noun`] of the form `[[k0 v0] [k1 v1] ... [kN vN] 0]` (a null-terminated map) to a
71///   [`HashMap`]`<$key_type, $val_type>`, returning [`Result`]`<`[`HashMap`]`<$key_type, $val_type>,
72///   `[`Error`]`>`.
73///
74///   `$key_type` and `$val_type` must each implement [`TryFrom`]`<`[`&Noun`]`>`.
75///
76///   The resulting [`HashMap`] does not include the null terminator.
77///
78/// ```
79/// # use axsys_noun::{cell::Cell, convert, noun::Noun};
80/// let noun = Noun::null();
81/// let map = convert!(&noun => HashMap<&str, &str>).unwrap();
82/// assert_eq!(map.len(), 0);
83/// ```
84///
85/// ```
86/// # use axsys_noun::{cell::Cell, convert, noun::Noun};
87/// let noun = Noun::from(Cell::from([
88///     Noun::from(Cell::from(["Ruth", "Babe"])),
89///     Noun::from(Cell::from(["Williams", "Ted"])),
90///     Noun::from(Cell::from(["Bonds", "Barry"])),
91///     Noun::from(Cell::from(["Pujols", "Albert"])),
92///     Noun::null()
93/// ]));
94/// let map = convert!(&noun => HashMap<&str, &str>).unwrap();
95/// assert_eq!(map.len(), 4);
96/// assert_eq!(map.get("Ruth"), Some(&"Babe"));
97/// assert_eq!(map.get("Williams"), Some(&"Ted"));
98/// assert_eq!(map.get("Bonds"), Some(&"Barry"));
99/// assert_eq!(map.get("Pujols"), Some(&"Albert"));
100/// ```
101///
102/// - Convert an iterator of the form `[e0, e1, ... eN]` where each element has type `T` into a
103///   [`Noun`] of the form `[e0 e1 ... eN 0]` (a null-terminated list), returning
104///   [`Result`]`<`[`Noun`]`, <err_type>>`, where `<err_type>` is the type of error returned by
105///   `Noun::try_from` when attempting to convert `T` into a [`Noun`].
106///
107///   [`Noun`] must implement [`TryFrom`]`<T>`.
108///
109/// ```
110/// # use axsys_noun::{atom::Atom, cell::Cell, convert, noun::Noun};
111/// let strings = [];
112/// let noun = convert!(strings.iter() => Noun).unwrap();
113/// assert!(noun.is_null());
114/// ```
115///
116/// ```
117/// # use axsys_noun::{atom::Atom, cell::Cell, convert, noun::Noun};
118/// let strings = vec![
119///     String::from("1"),
120///     String::from("2"),
121///     String::from("3"),
122///     String::from("4"),
123/// ];
124/// let noun = convert!(strings.into_iter() => Noun).unwrap();
125/// assert_eq!(
126///     noun,
127///     Noun::from(Cell::from([
128///         Atom::from("1"),
129///         Atom::from("2"),
130///         Atom::from("3"),
131///         Atom::from("4"),
132///         Atom::null(),
133///     ]))
134/// );
135/// ```
136///
137/// [`Err(Error)`]: Error
138/// [`HashMap`]: std::collections::HashMap
139/// [`&Noun`]: crate::Noun
140/// [`Noun`]: crate::Noun
141#[macro_export]
142macro_rules! convert {
143    ($noun:expr => Vec<$elem_type:ty>) => {{
144        use $crate::{convert::Error, noun::Noun};
145        let mut noun = $noun;
146        let mut elems: Vec<$elem_type> = Vec::new();
147        loop {
148            match noun {
149                Noun::Atom(atom) => {
150                    if atom.is_null() {
151                        break Ok(elems);
152                    } else {
153                        break Err(Error::ExpectedNull);
154                    }
155                }
156                Noun::Cell(cell) => match <$elem_type>::try_from(cell.head_ref()) {
157                    Ok(elem) => {
158                        elems.push(elem);
159                        noun = cell.tail_ref();
160                    }
161                    Err(err) => break Err(err),
162                },
163            }
164        }
165    }};
166    ($noun:expr => HashMap<$key_type:ty, $val_type:ty>) => {{
167        use std::collections::HashMap;
168        use $crate::{convert::Error, noun::Noun};
169        let mut noun = $noun;
170        let mut map: HashMap<$key_type, $val_type> = HashMap::new();
171        loop {
172            match noun {
173                Noun::Atom(atom) => {
174                    if atom.is_null() {
175                        break Ok(map);
176                    } else {
177                        break Err(Error::ExpectedNull);
178                    }
179                }
180                Noun::Cell(cell) => {
181                    if let Noun::Cell(head) = cell.head_ref() {
182                        match (
183                            <$key_type>::try_from(head.head_ref()),
184                            <$val_type>::try_from(head.tail_ref()),
185                        ) {
186                            (Ok(key), Ok(val)) => {
187                                map.insert(key, val);
188                                noun = cell.tail_ref();
189                            }
190                            (Err(err), _) => break Err(err),
191                            (_, Err(err)) => break Err(err),
192                        }
193                    } else {
194                        break Err(Error::UnexpectedAtom);
195                    }
196                }
197            }
198        }
199    }};
200    ($iter:expr => Noun) => {{
201        use $crate::{cell::Cell, noun::Noun, Rc};
202        let mut noun = Rc::<Noun>::from(Noun::null());
203        let mut iter = $iter.rev();
204        loop {
205            match iter.next() {
206                Some(elem) => match Noun::try_from(elem) {
207                    Ok(elem) => {
208                        noun = Rc::<Noun>::from(Noun::from(Cell::from([
209                            Rc::<Noun>::from(elem),
210                            noun,
211                        ])));
212                    }
213                    Err(err) => break Err(err),
214                },
215                None => break Ok(Rc::try_unwrap(noun).unwrap()),
216            }
217        }
218    }};
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::{atom::Atom, cell::Cell, noun::Noun};
224
225    #[test]
226    fn convert() {
227        // Noun -> Vec<String>: expect failure.
228        {
229            {
230                let noun = Noun::from(Cell::from(["no", "null", "terminator"]));
231                assert!(convert!(&noun => Vec<String>).is_err());
232            }
233
234            {
235                let noun = Noun::from(Cell::from([
236                    Noun::from(Cell::from(["unexpected", "cell"])),
237                    Noun::null(),
238                ]));
239                assert!(convert!(&noun => Vec<String>).is_err());
240            }
241        }
242
243        // &[&str] -> Noun: expect success.
244        {
245            {
246                let strings = ["a", "b", "c"];
247                let noun = convert!(strings.iter() => Noun).expect("&[str] to Noun");
248                assert_eq!(
249                    noun,
250                    Noun::from(Cell::from([
251                        Atom::from("a"),
252                        Atom::from("b"),
253                        Atom::from("c"),
254                        Atom::null()
255                    ]))
256                );
257            }
258        }
259    }
260}