1use std::fmt;
12
13use corium_core::{Keyword, TotalF64};
14use thiserror::Error;
15
16#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
18pub enum Edn {
19 Nil,
21 Bool(bool),
23 Long(i64),
25 Double(TotalF64),
27 Str(String),
29 Keyword(Keyword),
31 Symbol(String),
33 List(Vec<Edn>),
35 Vector(Vec<Edn>),
37 Map(Vec<(Edn, Edn)>),
39 Set(Vec<Edn>),
41 Tagged(String, Box<Edn>),
43}
44
45impl Edn {
46 #[must_use]
48 pub fn symbol(text: &str) -> Self {
49 Self::Symbol(text.to_owned())
50 }
51
52 #[must_use]
54 pub fn keyword(text: &str) -> Self {
55 Self::Keyword(Keyword::parse(text))
56 }
57
58 #[must_use]
60 pub fn as_symbol(&self) -> Option<&str> {
61 match self {
62 Self::Symbol(s) => Some(s),
63 _ => None,
64 }
65 }
66
67 #[must_use]
69 pub const fn as_keyword(&self) -> Option<&Keyword> {
70 match self {
71 Self::Keyword(k) => Some(k),
72 _ => None,
73 }
74 }
75
76 #[must_use]
78 pub fn as_seq(&self) -> Option<&[Edn]> {
79 match self {
80 Self::List(items) | Self::Vector(items) => Some(items),
81 _ => None,
82 }
83 }
84
85 #[must_use]
87 pub fn get(&self, key: &Self) -> Option<&Self> {
88 match self {
89 Self::Map(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
90 _ => None,
91 }
92 }
93}
94
95#[derive(Debug, Error, Eq, PartialEq)]
97#[error("EDN parse error at offset {offset}: {message}")]
98pub struct EdnError {
99 pub offset: usize,
101 pub message: String,
103}
104
105pub fn read_one(input: &str) -> Result<Edn, EdnError> {
110 let mut reader = Reader::new(input);
111 let form = reader.read_form()?;
112 reader.skip_ws();
113 if reader.peek().is_some() {
114 return Err(reader.error("trailing data after form"));
115 }
116 Ok(form)
117}
118
119pub fn read_all(input: &str) -> Result<Vec<Edn>, EdnError> {
124 let mut reader = Reader::new(input);
125 let mut forms = Vec::new();
126 loop {
127 reader.skip_ws();
128 if reader.peek().is_none() {
129 return Ok(forms);
130 }
131 forms.push(reader.read_form()?);
132 }
133}
134
135struct Reader<'a> {
136 bytes: &'a [u8],
137 pos: usize,
138}
139
140const DELIMITERS: &[u8] = b"()[]{}\"; \t\r\n,";
141
142impl<'a> Reader<'a> {
143 fn new(input: &'a str) -> Self {
144 Self {
145 bytes: input.as_bytes(),
146 pos: 0,
147 }
148 }
149
150 fn error(&self, message: &str) -> EdnError {
151 EdnError {
152 offset: self.pos,
153 message: message.to_owned(),
154 }
155 }
156
157 fn peek(&self) -> Option<u8> {
158 self.bytes.get(self.pos).copied()
159 }
160
161 fn bump(&mut self) -> Option<u8> {
162 let b = self.peek()?;
163 self.pos += 1;
164 Some(b)
165 }
166
167 fn skip_ws(&mut self) {
168 while let Some(b) = self.peek() {
169 match b {
170 b' ' | b'\t' | b'\r' | b'\n' | b',' => {
171 self.pos += 1;
172 }
173 b';' => {
174 while self.peek().is_some_and(|b| b != b'\n') {
175 self.pos += 1;
176 }
177 }
178 _ => return,
179 }
180 }
181 }
182
183 fn read_form(&mut self) -> Result<Edn, EdnError> {
184 self.skip_ws();
185 match self.peek().ok_or_else(|| self.error("unexpected end"))? {
186 b'(' => {
187 self.pos += 1;
188 Ok(Edn::List(self.read_until(b')')?))
189 }
190 b'[' => {
191 self.pos += 1;
192 Ok(Edn::Vector(self.read_until(b']')?))
193 }
194 b'{' => {
195 self.pos += 1;
196 let items = self.read_until(b'}')?;
197 if items.len() % 2 != 0 {
198 return Err(self.error("map requires an even number of forms"));
199 }
200 let mut pairs: Vec<(Edn, Edn)> = Vec::new();
201 let mut iter = items.into_iter();
202 while let (Some(k), Some(v)) = (iter.next(), iter.next()) {
203 pairs.push((k, v));
204 }
205 pairs.sort_by(|left, right| left.0.cmp(&right.0));
206 Ok(Edn::Map(pairs))
207 }
208 b'"' => self.read_string(),
209 b'#' => self.read_dispatch(),
210 _ => self.read_atom(),
211 }
212 }
213
214 fn read_until(&mut self, close: u8) -> Result<Vec<Edn>, EdnError> {
215 let mut items = Vec::new();
216 loop {
217 self.skip_ws();
218 match self.peek() {
219 None => return Err(self.error("unterminated collection")),
220 Some(b) if b == close => {
221 self.pos += 1;
222 return Ok(items);
223 }
224 Some(_) => items.push(self.read_form()?),
225 }
226 }
227 }
228
229 fn read_dispatch(&mut self) -> Result<Edn, EdnError> {
230 self.pos += 1;
231 match self.peek() {
232 Some(b'{') => {
233 self.pos += 1;
234 let mut items = self.read_until(b'}')?;
235 items.sort();
236 items.dedup();
237 Ok(Edn::Set(items))
238 }
239 Some(b'_') => {
240 self.pos += 1;
241 let _discarded = self.read_form()?;
242 self.read_form()
243 }
244 _ => {
245 let tag = self.read_token()?;
246 if tag.is_empty() {
247 return Err(self.error("empty dispatch tag"));
248 }
249 let value = self.read_form()?;
250 Ok(Edn::Tagged(tag, Box::new(value)))
251 }
252 }
253 }
254
255 fn read_string(&mut self) -> Result<Edn, EdnError> {
256 self.pos += 1;
257 let mut out = String::new();
258 loop {
259 match self
260 .bump()
261 .ok_or_else(|| self.error("unterminated string"))?
262 {
263 b'"' => return Ok(Edn::Str(out)),
264 b'\\' => {
265 let escape = self
266 .bump()
267 .ok_or_else(|| self.error("unterminated escape"))?;
268 out.push(match escape {
269 b'n' => '\n',
270 b't' => '\t',
271 b'r' => '\r',
272 b'"' => '"',
273 b'\\' => '\\',
274 _ => return Err(self.error("unknown string escape")),
275 });
276 }
277 b => {
278 let start = self.pos - 1;
280 let mut end = self.pos;
281 while end < self.bytes.len() && (self.bytes[end] & 0xC0) == 0x80 {
282 end += 1;
283 }
284 if b < 0x80 {
285 out.push(char::from(b));
286 } else {
287 let text = std::str::from_utf8(&self.bytes[start..end])
288 .map_err(|_| self.error("invalid UTF-8"))?;
289 out.push_str(text);
290 self.pos = end;
291 }
292 }
293 }
294 }
295 }
296
297 fn read_token(&mut self) -> Result<String, EdnError> {
298 let start = self.pos;
299 while self.peek().is_some_and(|b| !DELIMITERS.contains(&b)) {
300 self.pos += 1;
301 }
302 std::str::from_utf8(&self.bytes[start..self.pos])
303 .map(str::to_owned)
304 .map_err(|_| self.error("invalid UTF-8 token"))
305 }
306
307 fn read_atom(&mut self) -> Result<Edn, EdnError> {
308 let token = self.read_token()?;
309 if token.is_empty() {
310 return Err(self.error("unexpected character"));
311 }
312 if let Some(name) = token.strip_prefix(':') {
313 if name.is_empty() {
314 return Err(self.error("empty keyword"));
315 }
316 return Ok(Edn::keyword(name));
317 }
318 match token.as_str() {
319 "nil" => return Ok(Edn::Nil),
320 "true" => return Ok(Edn::Bool(true)),
321 "false" => return Ok(Edn::Bool(false)),
322 _ => {}
323 }
324 let numeric_start = token.starts_with(|c: char| c.is_ascii_digit())
325 || (token.len() > 1
326 && (token.starts_with('-') || token.starts_with('+'))
327 && token[1..].starts_with(|c: char| c.is_ascii_digit()));
328 if numeric_start {
329 if token.contains('.') || token.contains('e') || token.contains('E') {
330 return token
331 .parse::<f64>()
332 .map(|v| Edn::Double(TotalF64(v)))
333 .map_err(|_| self.error("malformed float"));
334 }
335 return token
336 .parse::<i64>()
337 .map(Edn::Long)
338 .map_err(|_| self.error("malformed integer"));
339 }
340 Ok(Edn::Symbol(token))
341 }
342}
343
344impl fmt::Display for Edn {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 fn write_seq(f: &mut fmt::Formatter<'_>, items: &[Edn]) -> fmt::Result {
347 for (i, item) in items.iter().enumerate() {
348 if i > 0 {
349 write!(f, " ")?;
350 }
351 write!(f, "{item}")?;
352 }
353 Ok(())
354 }
355 match self {
356 Self::Nil => write!(f, "nil"),
357 Self::Bool(v) => write!(f, "{v}"),
358 Self::Long(v) => write!(f, "{v}"),
359 Self::Double(v) => write!(f, "{:?}", v.0),
360 Self::Str(v) => write!(f, "{v:?}"),
361 Self::Keyword(v) => write!(f, "{v}"),
362 Self::Symbol(v) => write!(f, "{v}"),
363 Self::List(items) => {
364 write!(f, "(")?;
365 write_seq(f, items)?;
366 write!(f, ")")
367 }
368 Self::Vector(items) => {
369 write!(f, "[")?;
370 write_seq(f, items)?;
371 write!(f, "]")
372 }
373 Self::Set(items) => {
374 write!(f, "#{{")?;
375 write_seq(f, items)?;
376 write!(f, "}}")
377 }
378 Self::Map(pairs) => {
379 write!(f, "{{")?;
380 for (i, (k, v)) in pairs.iter().enumerate() {
381 if i > 0 {
382 write!(f, ", ")?;
383 }
384 write!(f, "{k} {v}")?;
385 }
386 write!(f, "}}")
387 }
388 Self::Tagged(tag, value) => write!(f, "#{tag} {value}"),
389 }
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn reads_query_shapes() {
399 let form = read_one(
400 "{:find [?e (count ?x)] ; comment\n :where [[?e :person/name \"A\"] [(< ?x 3)]]}",
401 )
402 .expect("parse");
403 assert!(form.get(&Edn::keyword("find")).is_some());
404 assert!(form.to_string().contains(":person/name"));
405 }
406
407 #[test]
408 fn reads_sets_tags_and_discards() {
409 assert_eq!(
410 read_one("#{3 1 2 1}").expect("set"),
411 Edn::Set(vec![Edn::Long(1), Edn::Long(2), Edn::Long(3)])
412 );
413 assert_eq!(
414 read_one("#tempid \"a\"").expect("tag"),
415 Edn::Tagged("tempid".into(), Box::new(Edn::Str("a".into())))
416 );
417 assert_eq!(
418 read_one("[#_ 1 2]").expect("discard"),
419 Edn::Vector(vec![Edn::Long(2)])
420 );
421 }
422
423 #[test]
424 fn reads_numbers_and_negative_symbols() {
425 assert_eq!(read_one("-42").expect("int"), Edn::Long(-42));
426 assert_eq!(read_one("1.5").expect("float"), Edn::Double(TotalF64(1.5)));
427 assert_eq!(read_one("-").expect("minus"), Edn::symbol("-"));
428 assert_eq!(read_one("?e").expect("var"), Edn::symbol("?e"));
429 }
430}