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}