Skip to main content

clojure_reader/
edn.rs

1//! An EDN reader/presenter in Rust.
2//!
3//! ## Implementations
4//! -  [`core::fmt::Display`] will output valid EDN for any Edn object
5//! -  With the `unstable` feature enabled, [`TryFrom`]<[`parse::Node`]> implemented for [`Edn`]
6//!    will convert the Node into an Edn
7//!
8//! ## Differences from Clojure
9//! -  Escape characters are not escaped.
10
11use alloc::boxed::Box;
12use alloc::collections::{BTreeMap, BTreeSet};
13use alloc::vec::Vec;
14use core::fmt;
15
16#[cfg(feature = "arbitrary-nums")]
17use bigdecimal::BigDecimal;
18#[cfg(feature = "arbitrary-nums")]
19use num_bigint::BigInt;
20#[cfg(feature = "floats")]
21use ordered_float::OrderedFloat;
22
23use crate::{error, parse};
24
25#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
26#[non_exhaustive]
27pub enum Edn<'e> {
28  Vector(Vec<Self>),
29  Set(BTreeSet<Self>),
30  Map(BTreeMap<Self, Self>),
31  List(Vec<Self>),
32  Key(&'e str),
33  Symbol(&'e str),
34  Str(&'e str),
35  Int(i64),
36  Tagged(&'e str, Box<Self>),
37  #[cfg(feature = "floats")]
38  Double(OrderedFloat<f64>),
39  Rational((i64, i64)),
40  #[cfg(feature = "arbitrary-nums")]
41  BigInt(BigInt),
42  #[cfg(feature = "arbitrary-nums")]
43  BigDec(BigDecimal),
44  Char(char),
45  Bool(bool),
46  Nil,
47}
48
49const SYMBOL_SPECIAL_CHARS: &str = ".*+!-_?$%&=<>:#";
50
51fn is_symbol_char(c: char) -> bool {
52  c.is_alphanumeric() || SYMBOL_SPECIAL_CHARS.contains(c)
53}
54
55fn is_symbol_start(c: char) -> bool {
56  !c.is_numeric() && !matches!(c, ':' | '#') && is_symbol_char(c)
57}
58
59fn valid_symbol_part(part: &str) -> bool {
60  let mut chars = part.chars();
61  let Some(first) = chars.next() else { return false };
62  let second = chars.clone().next();
63
64  is_symbol_start(first)
65    && !(matches!(first, '-' | '+' | '.') && second.is_some_and(char::is_numeric))
66    && chars.all(is_symbol_char)
67}
68
69pub(crate) fn validate_tag(tag: &str, tag_span: parse::Span) -> error::Result<()> {
70  let tag = tag.strip_prefix(':').unwrap_or(tag);
71  let valid = tag.chars().next().is_some_and(char::is_alphabetic)
72    && match tag.split_once('/') {
73      Some((prefix, name)) => {
74        !name.contains('/') && valid_symbol_part(prefix) && valid_symbol_part(name)
75      }
76      None => valid_symbol_part(tag),
77    };
78
79  if valid { Ok(()) } else { Err(error::Error::from_position(error::Code::InvalidTag, tag_span.0)) }
80}
81
82impl<'e> TryFrom<parse::Node<'e>> for Edn<'e> {
83  type Error = error::Error;
84  /// Elaborates a concrete [`Node`](parse::Node) into an abstract resolved [`Edn`]
85  ///
86  /// ```
87  /// #[cfg(feature = "unstable")]
88  /// {
89  ///   use clojure_reader::{parse, edn::Edn};
90  ///
91  ///   let edn: Edn = parse::Node::no_discards(parse::NodeKind::Nil, parse::Span::default())
92  ///     .try_into()
93  ///     .unwrap();
94  ///
95  ///   assert_eq!(edn, Edn::Nil);
96  /// }
97  /// ```
98  ///
99  /// # Errors
100  ///
101  /// See [`crate::error::Error`].
102  ///
103  /// [HMDK]: error::Code::HashMapDuplicateKey
104  /// [SDK]: error::Code::SetDuplicateKey
105  /// [IT]: error::Code::InvalidTag
106  fn try_from(parse::Node { kind: value, .. }: parse::Node<'e>) -> error::Result<Self> {
107    use error::{Code, Error, Result};
108    use parse::NodeKind;
109
110    Ok(match value {
111      NodeKind::Vector(items, _) => {
112        Edn::Vector(items.into_iter().map(TryInto::try_into).collect::<Result<_>>()?)
113      }
114      NodeKind::Set(items, _) => {
115        let mut set = BTreeSet::new();
116        for node in items {
117          let position = node.span().1;
118          if !set.insert(node.try_into()?) {
119            return Err(Error::from_position(Code::SetDuplicateKey, position));
120          }
121        }
122        Edn::Set(set)
123      }
124      NodeKind::Map(entries, _) => {
125        let mut map = BTreeMap::new();
126        for (key, value) in entries {
127          let position = value.span().1;
128          if map.insert(key.try_into()?, value.try_into()?).is_some() {
129            return Err(Error::from_position(Code::HashMapDuplicateKey, position));
130          }
131        }
132        Edn::Map(map)
133      }
134      NodeKind::List(items, _) => {
135        Edn::List(items.into_iter().map(TryInto::try_into).collect::<Result<_>>()?)
136      }
137      NodeKind::Key(key) => Edn::Key(key),
138      NodeKind::Symbol(symbol) => Edn::Symbol(symbol),
139      NodeKind::Str(str) => Edn::Str(str),
140      NodeKind::Int(int) => Edn::Int(int),
141      NodeKind::Tagged(tag, tag_span, node) => {
142        validate_tag(tag, tag_span)?;
143        if tag.starts_with(':') && !matches!(&node.kind, NodeKind::Map(..)) {
144          return Err(Error::from_position(Code::InvalidTag, tag_span.0));
145        }
146        Edn::Tagged(tag, Box::new((*node).try_into()?))
147      }
148      #[cfg(feature = "floats")]
149      NodeKind::Double(double) => Edn::Double(double),
150      NodeKind::Rational(rational) => Edn::Rational(rational),
151      #[cfg(feature = "arbitrary-nums")]
152      NodeKind::BigInt(big_int) => Edn::BigInt(big_int),
153      #[cfg(feature = "arbitrary-nums")]
154      NodeKind::BigDec(big_dec) => Edn::BigDec(big_dec),
155      NodeKind::Char(ch) => Edn::Char(ch),
156      NodeKind::Bool(bool) => Edn::Bool(bool),
157      NodeKind::Nil => Edn::Nil,
158    })
159  }
160}
161
162/// Reads one object from the &str.
163///
164/// # Errors
165///
166/// See [`crate::error::Error`].
167pub fn read_string(edn: &str) -> Result<Edn<'_>, error::Error> {
168  Ok(parse::parse_as_edn(edn)?.0)
169}
170
171/// Reads the first object from the &str and the remaining unread &str.
172///
173/// # Errors
174///
175/// Default behavior of Clojure's `read` is to throw an error on EOF, unlike `read_string`.
176/// <https://clojure.github.io/tools.reader/#clojure.tools.reader.edn/read>
177///
178/// See [`crate::error::Error`].
179pub fn read(edn: &str) -> Result<(Edn<'_>, &str), error::Error> {
180  let (edn, remaining) = parse::parse_optional_edn(edn)?;
181  let Some(edn) = edn else {
182    return Err(error::Error {
183      code: error::Code::UnexpectedEOF,
184      line: None,
185      column: None,
186      ptr: None,
187    });
188  };
189  Ok((edn, remaining))
190}
191
192fn get_tag<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
193  // Break out early if there's no namespaces
194  if !key.contains('/') {
195    return None;
196  }
197
198  // ignore the leading ':'
199  if !tag.starts_with(':') {
200    return None;
201  }
202  let tag = tag.get(1..)?;
203  Some(tag)
204}
205
206fn check_key<'a>(tag: &'a str, key: &'a str) -> &'a str {
207  // check if the Key starts with the saved Tag
208  if key.starts_with(tag) {
209    let (_, key) = key.rsplit_once(tag).expect("Tag must exist, because it starts with it.");
210
211    // ensure there's a '/' and strip it
212    if let Some(k) = key.strip_prefix('/') {
213      return k;
214    }
215  }
216  key
217}
218
219impl Edn<'_> {
220  pub fn get(&self, e: &Self) -> Option<&Self> {
221    if let Edn::Map(m) = self {
222      return m.get(e);
223    } else if let Edn::Tagged(tag, m) = self {
224      if let Edn::Key(key) = e {
225        let tag = get_tag(tag, key)?;
226        let key = check_key(tag, key);
227
228        return m.get(&Edn::Key(key));
229      }
230
231      // Cover cases where it's not a keyword
232      return m.get(e);
233    }
234    None
235  }
236  pub fn nth(&self, i: usize) -> Option<&Self> {
237    let vec = match self {
238      Edn::Vector(v) => v,
239      Edn::List(l) => l,
240      _ => return None,
241    };
242
243    vec.get(i)
244  }
245
246  pub fn contains(&self, e: &Self) -> bool {
247    match self {
248      Edn::Map(m) => m.contains_key(e),
249      Edn::Tagged(tag, m) => {
250        if let Edn::Key(key) = e {
251          let Some(tag) = get_tag(tag, key) else { return false };
252          let key = check_key(tag, key);
253
254          return m.contains(&Edn::Key(key));
255        }
256
257        // Cover cases where it's not a keyword
258        m.contains(e)
259      }
260      Edn::Vector(v) => v.contains(e),
261      Edn::Set(s) => s.contains(e),
262      Edn::List(l) => l.contains(e),
263      _ => false,
264    }
265  }
266}
267
268pub(crate) const fn char_to_edn(c: char) -> Option<&'static str> {
269  match c {
270    '\n' => Some("newline"),
271    '\r' => Some("return"),
272    ' ' => Some("space"),
273    '\t' => Some("tab"),
274    _ => None,
275  }
276}
277
278impl fmt::Display for Edn<'_> {
279  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280    match self {
281      Self::Vector(v) => {
282        write!(f, "[")?;
283        let mut it = v.iter().peekable();
284        while let Some(i) = it.next() {
285          if it.peek().is_some() {
286            write!(f, "{i} ")?;
287          } else {
288            write!(f, "{i}")?;
289          }
290        }
291        write!(f, "]")
292      }
293      Self::Set(s) => {
294        write!(f, "#{{")?;
295        let mut it = s.iter().peekable();
296        while let Some(i) = it.next() {
297          if it.peek().is_some() {
298            write!(f, "{i} ")?;
299          } else {
300            write!(f, "{i}")?;
301          }
302        }
303        write!(f, "}}")
304      }
305      Self::Map(m) => {
306        write!(f, "{{")?;
307        let mut it = m.iter().peekable();
308        while let Some(kv) = it.next() {
309          if it.peek().is_some() {
310            write!(f, "{} {}, ", kv.0, kv.1)?;
311          } else {
312            write!(f, "{} {}", kv.0, kv.1)?;
313          }
314        }
315        write!(f, "}}")
316      }
317      Self::List(l) => {
318        write!(f, "(")?;
319        let mut it = l.iter().peekable();
320        while let Some(i) = it.next() {
321          if it.peek().is_some() {
322            write!(f, "{i} ")?;
323          } else {
324            write!(f, "{i}")?;
325          }
326        }
327        write!(f, ")")
328      }
329      Self::Symbol(sy) => write!(f, "{sy}"),
330      Self::Tagged(t, s) => write!(f, "#{t} {s}"),
331      Self::Key(k) => write!(f, ":{k}"),
332      Self::Str(s) => write!(f, "\"{s}\""),
333      Self::Int(i) => write!(f, "{i}"),
334      #[cfg(feature = "floats")]
335      Self::Double(d) => write!(f, "{d}"),
336      #[cfg(feature = "arbitrary-nums")]
337      Self::BigInt(bi) => write!(f, "{bi}N"),
338      #[cfg(feature = "arbitrary-nums")]
339      Self::BigDec(bd) => write!(f, "{bd}M"),
340      Self::Rational((n, d)) => write!(f, "{n}/{d}"),
341      Self::Bool(b) => write!(f, "{b}"),
342      Self::Char(c) => {
343        write!(f, "\\")?;
344        if let Some(c) = char_to_edn(*c) {
345          return write!(f, "{c}");
346        }
347        write!(f, "{c}")
348      }
349      Self::Nil => write!(f, "nil"),
350    }
351  }
352}