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//!
6//! ## Differences from Clojure
7//! -  Escape characters are not escaped.
8
9use alloc::boxed::Box;
10use alloc::collections::{BTreeMap, BTreeSet};
11use alloc::vec::Vec;
12use core::fmt;
13
14#[cfg(feature = "arbitrary-nums")]
15use bigdecimal::BigDecimal;
16#[cfg(feature = "arbitrary-nums")]
17use num_bigint::BigInt;
18#[cfg(feature = "floats")]
19use ordered_float::OrderedFloat;
20
21use crate::{error, parse};
22
23#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
24#[non_exhaustive]
25pub enum Edn<'e> {
26  Vector(Vec<Edn<'e>>),
27  Set(BTreeSet<Edn<'e>>),
28  Map(BTreeMap<Edn<'e>, Edn<'e>>),
29  List(Vec<Edn<'e>>),
30  Key(&'e str),
31  Symbol(&'e str),
32  Str(&'e str),
33  Int(i64),
34  Tagged(&'e str, Box<Edn<'e>>),
35  #[cfg(feature = "floats")]
36  Double(OrderedFloat<f64>),
37  Rational((i64, i64)),
38  #[cfg(feature = "arbitrary-nums")]
39  BigInt(BigInt),
40  #[cfg(feature = "arbitrary-nums")]
41  BigDec(BigDecimal),
42  Char(char),
43  Bool(bool),
44  Nil,
45}
46
47/// Reads one object from the &str.
48///
49/// # Errors
50///
51/// See [`crate::error::Error`].
52pub fn read_string(edn: &str) -> Result<Edn<'_>, error::Error> {
53  Ok(parse::parse(edn)?.0)
54}
55
56/// Reads the first object from the &str and the remaining unread &str.
57///
58/// # Errors
59///
60/// Default behavior of Clojure's `read` is to throw an error on EOF, unlike `read_string`.
61/// <https://clojure.github.io/tools.reader/#clojure.tools.reader.edn/read>
62///
63/// See [`crate::error::Error`].
64pub fn read(edn: &str) -> Result<(Edn<'_>, &str), error::Error> {
65  let r = parse::parse(edn)?;
66  if r.0 == Edn::Nil && r.1.is_empty() {
67    return Err(error::Error {
68      code: error::Code::UnexpectedEOF,
69      line: None,
70      column: None,
71      ptr: None,
72    });
73  }
74  Ok((r.0, r.1))
75}
76
77fn get_tag<'a>(tag: &'a str, key: &'a str) -> Option<&'a str> {
78  // Break out early if there's no namespaces
79  if !key.contains('/') {
80    return None;
81  }
82
83  // ignore the leading ':'
84  if !tag.starts_with(':') {
85    return None;
86  }
87  let tag = tag.get(1..)?;
88  Some(tag)
89}
90
91fn check_key<'a>(tag: &'a str, key: &'a str) -> &'a str {
92  // check if the Key starts with the saved Tag
93  if key.starts_with(tag) {
94    let (_, key) = key.rsplit_once(tag).expect("Tag must exist, because it starts with it.");
95
96    // ensure there's a '/' and strip it
97    if let Some(k) = key.strip_prefix('/') {
98      return k;
99    }
100  }
101  key
102}
103
104impl Edn<'_> {
105  pub fn get(&self, e: &Self) -> Option<&Self> {
106    if let Edn::Map(m) = self {
107      return m.get(e);
108    } else if let Edn::Tagged(tag, m) = self {
109      if let Edn::Key(key) = e {
110        let tag = get_tag(tag, key)?;
111        let key = check_key(tag, key);
112
113        return m.get(&Edn::Key(key));
114      }
115
116      // Cover cases where it's not a keyword
117      return m.get(e);
118    }
119    None
120  }
121  pub fn nth(&self, i: usize) -> Option<&Self> {
122    let vec = match self {
123      Edn::Vector(v) => v,
124      Edn::List(l) => l,
125      _ => return None,
126    };
127
128    vec.get(i)
129  }
130
131  pub fn contains(&self, e: &Self) -> bool {
132    match self {
133      Edn::Map(m) => m.contains_key(e),
134      Edn::Tagged(tag, m) => {
135        if let Edn::Key(key) = e {
136          let Some(tag) = get_tag(tag, key) else { return false };
137          let key = check_key(tag, key);
138
139          return m.contains(&Edn::Key(key));
140        }
141
142        // Cover cases where it's not a keyword
143        m.contains(e)
144      }
145      Edn::Vector(v) => v.contains(e),
146      Edn::Set(s) => s.contains(e),
147      Edn::List(l) => l.contains(e),
148      _ => false,
149    }
150  }
151}
152
153pub(crate) const fn char_to_edn(c: char) -> Option<&'static str> {
154  match c {
155    '\n' => Some("newline"),
156    '\r' => Some("return"),
157    ' ' => Some("space"),
158    '\t' => Some("tab"),
159    _ => None,
160  }
161}
162
163impl fmt::Display for Edn<'_> {
164  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165    match self {
166      Self::Vector(v) => {
167        write!(f, "[")?;
168        let mut it = v.iter().peekable();
169        while let Some(i) = it.next() {
170          if it.peek().is_some() {
171            write!(f, "{i} ")?;
172          } else {
173            write!(f, "{i}")?;
174          }
175        }
176        write!(f, "]")
177      }
178      Self::Set(s) => {
179        write!(f, "#{{")?;
180        let mut it = s.iter().peekable();
181        while let Some(i) = it.next() {
182          if it.peek().is_some() {
183            write!(f, "{i} ")?;
184          } else {
185            write!(f, "{i}")?;
186          }
187        }
188        write!(f, "}}")
189      }
190      Self::Map(m) => {
191        write!(f, "{{")?;
192        let mut it = m.iter().peekable();
193        while let Some(kv) = it.next() {
194          if it.peek().is_some() {
195            write!(f, "{} {}, ", kv.0, kv.1)?;
196          } else {
197            write!(f, "{} {}", kv.0, kv.1)?;
198          }
199        }
200        write!(f, "}}")
201      }
202      Self::List(l) => {
203        write!(f, "(")?;
204        let mut it = l.iter().peekable();
205        while let Some(i) = it.next() {
206          if it.peek().is_some() {
207            write!(f, "{i} ")?;
208          } else {
209            write!(f, "{i}")?;
210          }
211        }
212        write!(f, ")")
213      }
214      Self::Symbol(sy) => write!(f, "{sy}"),
215      Self::Tagged(t, s) => write!(f, "#{t} {s}"),
216      Self::Key(k) => write!(f, ":{k}"),
217      Self::Str(s) => write!(f, "\"{s}\""),
218      Self::Int(i) => write!(f, "{i}"),
219      #[cfg(feature = "floats")]
220      Self::Double(d) => write!(f, "{d}"),
221      #[cfg(feature = "arbitrary-nums")]
222      Self::BigInt(bi) => write!(f, "{bi}N"),
223      #[cfg(feature = "arbitrary-nums")]
224      Self::BigDec(bd) => write!(f, "{bd}M"),
225      Self::Rational((n, d)) => write!(f, "{n}/{d}"),
226      Self::Bool(b) => write!(f, "{b}"),
227      Self::Char(c) => {
228        write!(f, "\\")?;
229        if let Some(c) = char_to_edn(*c) {
230          return write!(f, "{c}");
231        }
232        write!(f, "{c}")
233      }
234      Self::Nil => write!(f, "nil"),
235    }
236  }
237}