• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Grinkers / clojure-reader / 28671615325

03 Jul 2026 04:04PM UTC coverage: 98.978% (-0.1%) from 99.105%
28671615325

push

github

Grinkers
Fix reader/dispatch whitespace behavior, using `edn/read-string` as a reference for all tests

31 of 33 new or added lines in 2 files covered. (93.94%)

1452 of 1467 relevant lines covered (98.98%)

310.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.17
/src/parse.rs
1
//! An EDN syntax parser in Rust.
2
#![expect(clippy::inline_always)]
3

4
use alloc::boxed::Box;
5
use alloc::collections::{BTreeMap, BTreeSet};
6
use alloc::vec::Vec;
7
use core::mem::replace;
8
use core::primitive::str;
9

10
use crate::edn::Edn;
11
use crate::error::{Code, Error};
12

13
#[cfg(feature = "arbitrary-nums")]
14
use bigdecimal::BigDecimal;
15
#[cfg(feature = "arbitrary-nums")]
16
use num_bigint::BigInt;
17
#[cfg(feature = "floats")]
18
use ordered_float::OrderedFloat;
19

20
/// Possible kinds of an EDN node
21
///
22
/// **NOTE:** The vector of items in [`NodeKind::Set`] may contain duplicate items.
23
/// **NOTE:** The vector of entries in [`NodeKind::Map`] may contain duplicate keys.
24
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
25
#[non_exhaustive]
26
pub enum NodeKind<'e> {
27
  Vector(
28
    Vec<Node<'e>>,
29
    /* Any trailing discards inside vector, e.g. `[foo bar #_baz #_qux]` */ Vec<Discard<'e>>,
30
  ),
31
  Set(
32
    Vec<Node<'e>>,
33
    /* Any trailing discards inside set, e.g. `#{foo bar #_baz #_qux}` */ Vec<Discard<'e>>,
34
  ),
35
  Map(
36
    Vec<(Node<'e>, Node<'e>)>,
37
    /* Any trailing discards inside map, e.g. `{:foo bar #_baz #_qux}` */ Vec<Discard<'e>>,
38
  ),
39
  List(
40
    Vec<Node<'e>>,
41
    /* Any trailing discards inside list, e.g. `(foo bar #_baz #_qux)` */ Vec<Discard<'e>>,
42
  ),
43
  Key(&'e str),
44
  Symbol(&'e str),
45
  Str(&'e str),
46
  Int(i64),
47
  Tagged(&'e str, /* Span of the tag string */ Span, Box<Node<'e>>),
48
  #[cfg(feature = "floats")]
49
  Double(OrderedFloat<f64>),
50
  Rational((i64, i64)),
51
  #[cfg(feature = "arbitrary-nums")]
52
  BigInt(BigInt),
53
  #[cfg(feature = "arbitrary-nums")]
54
  BigDec(BigDecimal),
55
  Char(char),
56
  Bool(bool),
57
  #[default]
58
  Nil,
59
}
60

61
/// A **discarded** form containing the node that was discarded
62
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
63
pub struct Discard<'e>(pub Node<'e>, pub Span);
64

65
/// Concrete EDN syntax tree.
66
///
67
/// Parse one with [`parse`], then convert it to an [`Edn`] with [`Edn::try_from`].
68
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
69
pub struct Node<'e> {
70
  pub kind: NodeKind<'e>,
71
  pub span: Span,
72
  pub leading_discards: Vec<Discard<'e>>,
73
}
74

75
impl<'e> Node<'e> {
76
  /// Construct a `Node` with the given kind and span and no leading discards.
77
  pub const fn no_discards(kind: NodeKind<'e>, span: Span) -> Self {
900✔
78
    Self { kind, span, leading_discards: Vec::new() }
900✔
79
  }
900✔
80

81
  #[inline]
82
  pub const fn span(&self) -> Span {
14✔
83
    self.span
14✔
84
  }
14✔
85
}
86

87
/// Parse a single `Node` from a [`SourceReader`], consuming that form.
88
///
89
/// # Examples
90
///
91
/// ```
92
/// #[cfg(feature = "unstable")]
93
/// {
94
///   use clojure_reader::parse::{Node, NodeKind::*, SourceReader, parse};
95
///
96
///   let source = r#"
97
/// (->> txs
98
///   (keep :refund-amt)
99
///   (reduce +))
100
///   ; total refund amount
101
/// "#;
102
///   let mut reader = SourceReader::new(source);
103
///   let Node { kind: node, .. } = parse(&mut reader).expect("failed to parse");
104
///
105
///   let List(nodes, _) = node else { panic!("unexpected") };
106
///
107
///   // Destruct main list
108
///   let nodes: Vec<_> = nodes.into_iter().map(|n| n.kind).collect();
109
///   let [Symbol("->>"), Symbol("txs"), List(keep, _), List(reduce, _)] = nodes.as_slice() else {
110
///     panic!("unexpected");
111
///   };
112
///
113
///   // Destruct the list calling `keep`
114
///   let keep: Vec<_> = keep.into_iter().map(|n| &n.kind).collect();
115
///   let [Symbol("keep"), Key("refund-amt")] = keep.as_slice() else {
116
///     panic!("unexpected");
117
///   };
118
///
119
///   // Destruct the list calling `reduce`
120
///   let reduce: Vec<_> = reduce.into_iter().map(|n| &n.kind).collect();
121
///   let [Symbol("reduce"), Symbol("+")] = reduce.as_slice() else {
122
///     panic!("unexpected");
123
///   };
124
///
125
///   assert_eq!(reader.remaining(), "\n  ; total refund amount\n");
126
/// }
127
/// ```
128
///
129
/// # Errors
130
///
131
/// See [`crate::error::Error`].
132
#[cfg_attr(not(feature = "unstable"), expect(dead_code))]
133
pub fn parse<'r, 'e: 'r>(reader: &'r mut SourceReader<'e>) -> Result<Node<'e>, Error> {
120✔
134
  let start_pos = reader.read_pos;
120✔
135
  let builder = NodeBuilder;
120✔
136
  let parsed = {
115✔
137
    let mut walker = Walker::new(reader);
120✔
138
    parse_internal(&mut walker, &builder)?
120✔
139
  };
140
  Ok(parsed.unwrap_or_else(|| builder.nil(reader.span_from(start_pos))))
115✔
141
}
120✔
142

143
/// Parse the first EDN form from a string and return it with the unread remainder.
144
///
145
/// # Errors
146
///
147
/// See [`crate::error::Error`].
148
pub fn parse_as_edn(edn: &str) -> Result<(Edn<'_>, &str), Error> {
350✔
149
  let mut source_reader = SourceReader::new(edn);
350✔
150
  let start_pos = source_reader.read_pos;
350✔
151
  let builder = EdnBuilder;
350✔
152
  let parsed = {
260✔
153
    let mut walker = Walker::new(&mut source_reader);
350✔
154
    parse_internal(&mut walker, &builder)?
350✔
155
  };
156
  let parsed = parsed.unwrap_or_else(|| builder.nil(source_reader.span_from(start_pos)));
260✔
157
  Ok((parsed, source_reader.remaining()))
260✔
158
}
350✔
159

160
#[cfg_attr(not(feature = "unstable"), expect(clippy::redundant_pub_crate))]
161
pub(crate) fn parse_optional_edn(edn: &str) -> Result<(Option<Edn<'_>>, &str), Error> {
25✔
162
  let mut source_reader = SourceReader::new(edn);
25✔
163
  let parsed = {
25✔
164
    let mut walker = Walker::new(&mut source_reader);
25✔
165
    parse_internal(&mut walker, &EdnBuilder)?
25✔
166
  };
167
  Ok((parsed, source_reader.remaining()))
25✔
168
}
25✔
169

170
const DELIMITERS: [char; 8] = [',', ']', '}', ')', ';', '(', '[', '{'];
171

172
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
173
pub struct Position {
174
  pub line: usize,
175
  pub column: usize,
176
  pub ptr: usize,
177
}
178

179
impl Default for Position {
180
  fn default() -> Self {
494✔
181
    Self { line: 1, column: 1, ptr: 0 }
494✔
182
  }
494✔
183
}
184

185
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
186
pub struct Span(pub Position, pub Position);
187

188
impl Span {
189
  /// Whether the span is empty
190
  pub const fn is_empty(&self) -> bool {
2✔
191
    self.0.ptr == self.1.ptr
2✔
192
  }
2✔
193
}
194

195
/// A string-slice reader that records how much of the slice has been read
196
#[derive(Debug)]
197
pub struct SourceReader<'s> {
198
  slice: &'s str,
199
  // Position till where this string has been read
200
  read_pos: Position,
201
}
202

203
impl<'e> SourceReader<'e> {
204
  pub fn new(source: &'e str) -> Self {
480✔
205
    Self { slice: source, read_pos: Position::default() }
480✔
206
  }
480✔
207

208
  /// Span from some previously marked position to the current position of the reader
209
  #[inline(always)]
210
  pub const fn span_from(&self, marker: Position) -> Span {
1,370✔
211
    Span(marker, self.read_pos)
1,370✔
212
  }
1,370✔
213

214
  /// The portion of the source-string remaining to be read
215
  ///
216
  /// ```
217
  /// #[cfg(feature = "unstable")]
218
  /// {
219
  ///   use clojure_reader::parse::{SourceReader, parse};
220
  ///
221
  ///   let mut s = SourceReader::new("() []");
222
  ///   let _ = parse(&mut s).expect("failed to parse");
223
  ///   assert_eq!(s.remaining(), " []");
224
  /// }
225
  /// ```
226
  pub fn remaining(&self) -> &'e str {
286✔
227
    &self.slice[self.read_pos.ptr..]
286✔
228
  }
286✔
229

230
  /// Finishes the source-reader, returning:
231
  /// 1. the current reader position, and
232
  /// 2. the original source-string.
233
  ///
234
  /// ```
235
  /// #[cfg(feature = "unstable")]
236
  /// {
237
  ///   use clojure_reader::parse::{Position, SourceReader, parse};
238
  ///
239
  ///   let mut s = SourceReader::new("() []");
240
  ///   let _ = parse(&mut s).expect("failed to parse");
241
  ///
242
  ///   let (pos, slice) = s.finish();
243
  ///   assert_eq!(
244
  ///       pos,
245
  ///       Position {
246
  ///           line: 1,
247
  ///           column: 3,
248
  ///           ptr: 2
249
  ///       }
250
  ///   );
251
  ///   assert_eq!(slice, "() []");
252
  ///   assert_eq!(&slice[pos.ptr..], " []");
253
  /// }
254
  /// ```
255
  #[cfg_attr(not(feature = "unstable"), expect(dead_code))]
256
  pub const fn finish(self) -> (Position, &'e str) {
1✔
257
    (self.read_pos, self.slice)
1✔
258
  }
1✔
259

260
  // Slurps until whitespace or delimiter, returning the slice.
261
  #[inline(always)]
262
  fn slurp_literal(&mut self) -> &'e str {
1,050✔
263
    let token = self.slice[self.read_pos.ptr..]
1,050✔
264
      .split(|c: char| c.is_whitespace() || DELIMITERS.contains(&c) || c == '"')
6,465✔
265
      .next()
1,050✔
266
      .expect("Expected at least an empty slice");
1,050✔
267

268
    self.read_pos.ptr += token.len();
1,050✔
269
    self.read_pos.column += token.chars().count();
1,050✔
270
    token
1,050✔
271
  }
1,050✔
272

273
  // Slurps a char. Special handling for chars that happen to be delimiters
274
  #[inline(always)]
275
  fn slurp_char(&mut self) -> &'e str {
143✔
276
    let starting_ptr = self.read_pos.ptr;
143✔
277

278
    let mut ptr = 0;
143✔
279
    while let Some(c) = self.peek_next() {
537✔
280
      // first is always \\, second is always a char we want.
281
      // Handles edge cases of having a valid "\\[" but also "\\c[lolthisisvalidedn"
282
      if ptr > 1 && (c.is_whitespace() || DELIMITERS.contains(&c)) {
535✔
283
        break;
141✔
284
      }
394✔
285

286
      let _ = self.nibble_next();
394✔
287
      ptr += c.len_utf8();
394✔
288
    }
289
    &self.slice[starting_ptr..starting_ptr + ptr]
143✔
290
  }
143✔
291

292
  #[inline(always)]
293
  fn slurp_str(&mut self) -> Result<&'e str, Error> {
184✔
294
    let _ = self.nibble_next(); // Consume the leading '"' char
184✔
295
    let starting_ptr = self.read_pos.ptr;
184✔
296
    let mut escape = false;
184✔
297
    loop {
298
      if let Some(c) = self.nibble_next() {
1,349✔
299
        if escape {
1,339✔
300
          match c {
6✔
301
            't' | 'r' | 'n' | '\\' | '"' => (),
4✔
302
            _ => {
303
              return Err(Error::from_position(Code::InvalidEscape, self.read_pos));
2✔
304
            }
305
          }
306
          escape = false;
4✔
307
        } else if c == '"' {
1,333✔
308
          return Ok(&self.slice[starting_ptr..self.read_pos.ptr - 1]);
172✔
309
        } else {
1,161✔
310
          escape = c == '\\';
1,161✔
311
        }
1,161✔
312
      } else {
313
        return Err(Error::from_position(Code::UnexpectedEOF, self.read_pos));
10✔
314
      }
315
    }
316
  }
184✔
317

318
  #[inline(always)]
319
  fn slurp_tag(&mut self) -> Result<&'e str, Error> {
100✔
320
    let starting_ptr = self.read_pos.ptr;
100✔
321

322
    loop {
323
      if let Some(c) = self.peek_next() {
524✔
324
        if c.is_whitespace() || DELIMITERS.contains(&c) {
520✔
325
          return Ok(&self.slice[starting_ptr..self.read_pos.ptr]);
96✔
326
        }
424✔
327
        let _ = self.nibble_next();
424✔
328
      } else {
329
        return Err(Error::from_position(Code::UnexpectedEOF, self.read_pos));
4✔
330
      }
331
    }
332
  }
100✔
333

334
  // Nibbles away until the next new line
335
  #[inline(always)]
336
  fn nibble_newline(&mut self) {
24✔
337
    while let Some(c) = self.peek_next() {
270✔
338
      if matches!(c, '\n' | '\r') {
268✔
339
        break;
22✔
340
      }
246✔
341
      let _ = self.nibble_next();
246✔
342
    }
343
    self.nibble_whitespace();
24✔
344
  }
24✔
345

346
  // Nibbles away until the start of the next form
347
  #[inline(always)]
348
  fn nibble_whitespace(&mut self) {
4,630✔
349
    while let Some(n) = self.peek_next() {
6,771✔
350
      if n == ',' || n.is_whitespace() {
6,711✔
351
        let _ = self.nibble_next();
2,141✔
352
        continue;
2,141✔
353
      }
4,570✔
354
      break;
4,570✔
355
    }
356
  }
4,630✔
357

358
  // Consumes next
359
  #[inline(always)]
360
  fn nibble_next(&mut self) -> Option<char> {
7,938✔
361
    let char = self.slice[self.read_pos.ptr..].chars().next();
7,938✔
362
    if let Some(c) = char {
7,938✔
363
      self.read_pos.ptr += c.len_utf8();
7,928✔
364
      if c == '\n' {
7,928✔
365
        self.read_pos.line += 1;
148✔
366
        self.read_pos.column = 1;
148✔
367
      } else {
7,780✔
368
        self.read_pos.column += 1;
7,780✔
369
      }
7,780✔
370
    }
10✔
371
    char
7,938✔
372
  }
7,938✔
373

374
  // Peek into the next char
375
  #[inline(always)]
376
  fn peek_next(&self) -> Option<char> {
12,878✔
377
    self.slice[self.read_pos.ptr..].chars().next()
12,878✔
378
  }
12,878✔
379
}
380

381
struct Parsed<I> {
382
  item: I,
383
  span: Span,
384
}
385

386
impl<I> Parsed<I> {
387
  const fn new(item: I, span: Span) -> Self {
1,809✔
388
    Self { item, span }
1,809✔
389
  }
1,809✔
390
}
391

392
struct Walker<'e, 'r, B: InternalParser<'e>> {
393
  reader: &'r mut SourceReader<'e>,
394
  stack: Vec<ParseContext<'e, B>>,
395
}
396

397
impl<'e, 'r, B: InternalParser<'e>> Walker<'e, 'r, B> {
398
  fn new(reader: &'r mut SourceReader<'e>) -> Self {
495✔
399
    Self {
495✔
400
      reader,
495✔
401
      stack: alloc::vec![ParseContext { kind: ContextKind::Top, discards: Vec::new() }],
495✔
402
    }
495✔
403
  }
495✔
404

405
  #[inline(always)]
406
  const fn pos(&self) -> Position {
2,950✔
407
    self.reader.read_pos
2,950✔
408
  }
2,950✔
409

410
  /// Span from some previously marked position to the current position of the walker's reader
411
  #[inline(always)]
412
  const fn span_from(&self, marker: Position) -> Span {
574✔
413
    Span(marker, self.reader.read_pos)
574✔
414
  }
574✔
415

416
  #[inline(always)]
417
  fn push_context(&mut self, ctx: ParseContext<'e, B>) {
2,640✔
418
    self.stack.push(ctx);
2,640✔
419
  }
2,640✔
420

421
  #[inline(always)]
422
  fn pop_context(&mut self) -> Option<ParseContext<'e, B>> {
595✔
423
    self.stack.pop()
595✔
424
  }
595✔
425

426
  #[inline(always)]
427
  const fn stack_len(&self) -> usize {
2,180✔
428
    self.stack.len()
2,180✔
429
  }
2,180✔
430

431
  const fn make_error(&self, code: Code) -> Error {
45✔
432
    Error::from_position(code, self.pos())
45✔
433
  }
45✔
434

435
  fn last_context_discards(&mut self) -> Option<&mut Vec<B::Discard>> {
370✔
436
    match self.stack.last_mut() {
370✔
437
      Some(ParseContext { discards, .. }) => Some(discards),
370✔
438
      None => None,
×
439
    }
440
  }
370✔
441
}
442

443
#[derive(Debug, Clone, Copy)]
444
enum OpenDelimiter {
445
  Vector,
446
  List,
447
  Map,
448
  Hash,
449
}
450

451
// `Position`, wherever present, contains the start position of that context
452
enum ContextKind<'e, B: InternalParser<'e>> {
453
  Top,
454
  Vector(B::VectorContext, Position),
455
  List(B::ListContext, Position),
456
  Map(B::MapContext, Position),
457
  Set(B::SetContext, Position),
458
  Tag(&'e str, /* Span of the tag string */ Span, Position),
459
  Discard(Position),
460
}
461

462
struct ParseContext<'e, B: InternalParser<'e>> {
463
  kind: ContextKind<'e, B>,
464
  discards: Vec<B::Discard>,
465
}
466

467
impl<'e, B: InternalParser<'e>> ParseContext<'e, B> {
468
  const fn no_discards(kind: ContextKind<'e, B>) -> Self {
2,640✔
469
    Self { kind, discards: Vec::new() }
2,640✔
470
  }
2,640✔
471
}
472

473
#[derive(Debug, Clone)]
474
#[cfg_attr(not(feature = "arbitrary-nums"), derive(Copy))]
475
enum Atom<'e> {
476
  Key(&'e str),
477
  Symbol(&'e str),
478
  Str(&'e str),
479
  Int(i64),
480
  #[cfg(feature = "floats")]
481
  Double(OrderedFloat<f64>),
482
  Rational((i64, i64)),
483
  #[cfg(feature = "arbitrary-nums")]
484
  BigInt(BigInt),
485
  #[cfg(feature = "arbitrary-nums")]
486
  BigDec(BigDecimal),
487
  Char(char),
488
  Bool(bool),
489
  Nil,
490
}
491

492
trait InternalParser<'e> {
493
  type Item;
494
  type Discard;
495
  type VectorContext;
496
  type ListContext;
497
  type MapContext;
498
  type SetContext;
499

500
  fn atom(&self, atom: Atom<'e>, span: Span) -> Self::Item;
501

502
  fn with_leading_discards(
503
    &self,
504
    item: Self::Item,
505
    leading_discards: Vec<Self::Discard>,
506
  ) -> Self::Item;
507

508
  fn new_vector_context(&self) -> Self::VectorContext;
509

510
  fn new_list_context(&self) -> Self::ListContext;
511

512
  fn new_map_context(&self) -> Self::MapContext;
513

514
  fn new_set_context(&self) -> Self::SetContext;
515

516
  fn add_to_vector(
517
    &self,
518
    ctx: &mut Self::VectorContext,
519
    parsed: Parsed<Self::Item>,
520
    leading_discards: Vec<Self::Discard>,
521
  ) -> Result<(), Error>;
522

523
  fn add_to_list(
524
    &self,
525
    ctx: &mut Self::ListContext,
526
    parsed: Parsed<Self::Item>,
527
    leading_discards: Vec<Self::Discard>,
528
  ) -> Result<(), Error>;
529

530
  fn add_to_map(
531
    &self,
532
    ctx: &mut Self::MapContext,
533
    parsed: Parsed<Self::Item>,
534
    leading_discards: Vec<Self::Discard>,
535
  ) -> Result<(), Error>;
536

537
  fn add_to_set(
538
    &self,
539
    ctx: &mut Self::SetContext,
540
    parsed: Parsed<Self::Item>,
541
    leading_discards: Vec<Self::Discard>,
542
  ) -> Result<(), Error>;
543

544
  fn finish_vector(
545
    &self,
546
    ctx: Self::VectorContext,
547
    trailing_discards: Vec<Self::Discard>,
548
    span: Span,
549
  ) -> Result<Parsed<Self::Item>, Error>;
550

551
  fn finish_set(
552
    &self,
553
    ctx: Self::SetContext,
554
    trailing_discards: Vec<Self::Discard>,
555
    validate: bool,
556
    span: Span,
557
  ) -> Result<Parsed<Self::Item>, Error>;
558

559
  fn finish_map(
560
    &self,
561
    ctx: Self::MapContext,
562
    trailing_discards: Vec<Self::Discard>,
563
    validate: bool,
564
    close_pos: Position,
565
    span: Span,
566
  ) -> Result<Parsed<Self::Item>, Error>;
567

568
  fn finish_list(
569
    &self,
570
    ctx: Self::ListContext,
571
    trailing_discards: Vec<Self::Discard>,
572
    span: Span,
573
  ) -> Result<Parsed<Self::Item>, Error>;
574

575
  fn tag(
576
    &self,
577
    tag: &'e str,
578
    tag_span: Span,
579
    value: Parsed<Self::Item>,
580
    leading_discards: Vec<Self::Discard>,
581
    span: Span,
582
  ) -> Parsed<Self::Item>;
583

584
  fn discard(
585
    &self,
586
    value: Parsed<Self::Item>,
587
    leading_discards: Vec<Self::Discard>,
588
    discard_span: Span,
589
  ) -> Self::Discard;
590

591
  fn nil(&self, span: Span) -> Self::Item {
24✔
592
    self.atom(Atom::Nil, span)
24✔
593
  }
24✔
594
}
595

596
struct EdnBuilder;
597

598
impl<'e> InternalParser<'e> for EdnBuilder {
599
  type Item = Edn<'e>;
600
  type Discard = ();
601
  type VectorContext = Vec<Edn<'e>>;
602
  type ListContext = Vec<Edn<'e>>;
603
  type MapContext = (Vec<(Parsed<Edn<'e>>, Parsed<Edn<'e>>)>, Option<Parsed<Edn<'e>>>);
604
  type SetContext = Vec<Parsed<Edn<'e>>>;
605

606
  fn atom(&self, atom: Atom<'e>, span: Span) -> Self::Item {
991✔
607
    let _ = span;
991✔
608
    match atom {
991✔
609
      Atom::Key(key) => Edn::Key(key),
253✔
610
      Atom::Symbol(symbol) => Edn::Symbol(symbol),
122✔
611
      Atom::Str(str) => Edn::Str(str),
133✔
612
      Atom::Int(int) => Edn::Int(int),
321✔
613
      #[cfg(feature = "floats")]
614
      Atom::Double(double) => Edn::Double(double),
8✔
615
      Atom::Rational(rational) => Edn::Rational(rational),
21✔
616
      #[cfg(feature = "arbitrary-nums")]
617
      Atom::BigInt(big_int) => Edn::BigInt(big_int),
7✔
618
      #[cfg(feature = "arbitrary-nums")]
619
      Atom::BigDec(big_dec) => Edn::BigDec(big_dec),
7✔
620
      Atom::Char(ch) => Edn::Char(ch),
76✔
621
      Atom::Bool(bool) => Edn::Bool(bool),
12✔
622
      Atom::Nil => Edn::Nil,
31✔
623
    }
624
  }
991✔
625

626
  fn with_leading_discards(
261✔
627
    &self,
261✔
628
    item: Self::Item,
261✔
629
    _leading_discards: Vec<Self::Discard>,
261✔
630
  ) -> Self::Item {
261✔
631
    item
261✔
632
  }
261✔
633

634
  fn new_vector_context(&self) -> Self::VectorContext {
1,569✔
635
    Vec::new()
1,569✔
636
  }
1,569✔
637

638
  fn new_list_context(&self) -> Self::ListContext {
559✔
639
    Vec::new()
559✔
640
  }
559✔
641

642
  fn new_map_context(&self) -> Self::MapContext {
159✔
643
    (Vec::new(), None)
159✔
644
  }
159✔
645

646
  fn new_set_context(&self) -> Self::SetContext {
30✔
647
    Vec::new()
30✔
648
  }
30✔
649

650
  fn add_to_vector(
202✔
651
    &self,
202✔
652
    ctx: &mut Self::VectorContext,
202✔
653
    parsed: Parsed<Self::Item>,
202✔
654
    _leading_discards: Vec<Self::Discard>,
202✔
655
  ) -> Result<(), Error> {
202✔
656
    ctx.push(parsed.item);
202✔
657
    Ok(())
202✔
658
  }
202✔
659

660
  fn add_to_list(
177✔
661
    &self,
177✔
662
    ctx: &mut Self::ListContext,
177✔
663
    parsed: Parsed<Self::Item>,
177✔
664
    _leading_discards: Vec<Self::Discard>,
177✔
665
  ) -> Result<(), Error> {
177✔
666
    ctx.push(parsed.item);
177✔
667
    Ok(())
177✔
668
  }
177✔
669

670
  fn add_to_map(
492✔
671
    &self,
492✔
672
    ctx: &mut Self::MapContext,
492✔
673
    parsed: Parsed<Self::Item>,
492✔
674
    _leading_discards: Vec<Self::Discard>,
492✔
675
  ) -> Result<(), Error> {
492✔
676
    let (entries, pending) = ctx;
492✔
677
    if let Some(key) = pending.take() {
492✔
678
      entries.push((key, parsed));
241✔
679
    } else {
251✔
680
      *pending = Some(parsed);
251✔
681
    }
251✔
682
    Ok(())
492✔
683
  }
492✔
684

685
  fn add_to_set(
80✔
686
    &self,
80✔
687
    ctx: &mut Self::SetContext,
80✔
688
    parsed: Parsed<Self::Item>,
80✔
689
    _leading_discards: Vec<Self::Discard>,
80✔
690
  ) -> Result<(), Error> {
80✔
691
    ctx.push(parsed);
80✔
692
    Ok(())
80✔
693
  }
80✔
694

695
  fn finish_vector(
63✔
696
    &self,
63✔
697
    ctx: Self::VectorContext,
63✔
698
    _trailing_discards: Vec<Self::Discard>,
63✔
699
    span: Span,
63✔
700
  ) -> Result<Parsed<Self::Item>, Error> {
63✔
701
    Ok(Parsed::new(Edn::Vector(ctx), span))
63✔
702
  }
63✔
703

704
  fn finish_set(
22✔
705
    &self,
22✔
706
    ctx: Self::SetContext,
22✔
707
    _trailing_discards: Vec<Self::Discard>,
22✔
708
    validate: bool,
22✔
709
    span: Span,
22✔
710
  ) -> Result<Parsed<Self::Item>, Error> {
22✔
711
    let mut set = BTreeSet::new();
22✔
712
    for item in ctx {
64✔
713
      if !set.insert(item.item) && validate {
64✔
714
        return Err(Error::from_position(Code::SetDuplicateKey, item.span.1));
4✔
715
      }
60✔
716
    }
717
    Ok(Parsed::new(Edn::Set(set), span))
18✔
718
  }
22✔
719

720
  fn finish_map(
125✔
721
    &self,
125✔
722
    ctx: Self::MapContext,
125✔
723
    _trailing_discards: Vec<Self::Discard>,
125✔
724
    validate: bool,
125✔
725
    close_pos: Position,
125✔
726
    span: Span,
125✔
727
  ) -> Result<Parsed<Self::Item>, Error> {
125✔
728
    if ctx.1.is_some() {
125✔
729
      return Err(Error::from_position(Code::UnexpectedEOF, close_pos));
2✔
730
    }
123✔
731
    let mut map = BTreeMap::new();
123✔
732
    for (key, value) in ctx.0 {
225✔
733
      if map.insert(key.item, value.item).is_some() && validate {
225✔
734
        return Err(Error::from_position(Code::HashMapDuplicateKey, value.span.1));
8✔
735
      }
217✔
736
    }
737
    Ok(Parsed::new(Edn::Map(map), span))
115✔
738
  }
125✔
739

740
  fn finish_list(
82✔
741
    &self,
82✔
742
    ctx: Self::ListContext,
82✔
743
    _trailing_discards: Vec<Self::Discard>,
82✔
744
    span: Span,
82✔
745
  ) -> Result<Parsed<Self::Item>, Error> {
82✔
746
    Ok(Parsed::new(Edn::List(ctx), span))
82✔
747
  }
82✔
748

749
  fn tag(
67✔
750
    &self,
67✔
751
    tag: &'e str,
67✔
752
    _tag_span: Span,
67✔
753
    value: Parsed<Self::Item>,
67✔
754
    _leading_discards: Vec<Self::Discard>,
67✔
755
    span: Span,
67✔
756
  ) -> Parsed<Self::Item> {
67✔
757
    Parsed::new(Edn::Tagged(tag, Box::new(value.item)), span)
67✔
758
  }
67✔
759

760
  fn discard(
39✔
761
    &self,
39✔
762
    _value: Parsed<Self::Item>,
39✔
763
    _leading_discards: Vec<Self::Discard>,
39✔
764
    _discard_span: Span,
39✔
765
  ) -> Self::Discard {
39✔
766
  }
39✔
767
}
768

769
struct NodeBuilder;
770

771
impl<'e> InternalParser<'e> for NodeBuilder {
772
  type Item = Node<'e>;
773
  type Discard = Discard<'e>;
774
  type VectorContext = Vec<Node<'e>>;
775
  type ListContext = Vec<Node<'e>>;
776
  type MapContext = (Vec<(Node<'e>, Node<'e>)>, Option<Node<'e>>);
777
  type SetContext = Vec<Node<'e>>;
778

779
  fn atom(&self, atom: Atom<'e>, span: Span) -> Self::Item {
379✔
780
    let kind = match atom {
379✔
781
      Atom::Key(key) => NodeKind::Key(key),
65✔
782
      Atom::Symbol(symbol) => NodeKind::Symbol(symbol),
97✔
783
      Atom::Str(str) => NodeKind::Str(str),
39✔
784
      Atom::Int(int) => NodeKind::Int(int),
78✔
785
      #[cfg(feature = "floats")]
786
      Atom::Double(double) => NodeKind::Double(double),
2✔
787
      Atom::Rational(rational) => NodeKind::Rational(rational),
12✔
788
      #[cfg(feature = "arbitrary-nums")]
789
      Atom::BigInt(big_int) => NodeKind::BigInt(big_int),
1✔
790
      #[cfg(feature = "arbitrary-nums")]
791
      Atom::BigDec(big_dec) => NodeKind::BigDec(big_dec),
1✔
792
      Atom::Char(ch) => NodeKind::Char(ch),
65✔
793
      Atom::Bool(bool) => NodeKind::Bool(bool),
7✔
794
      Atom::Nil => NodeKind::Nil,
12✔
795
    };
796

797
    Node::no_discards(kind, span)
379✔
798
  }
379✔
799

800
  fn with_leading_discards(
491✔
801
    &self,
491✔
802
    mut item: Self::Item,
491✔
803
    leading_discards: Vec<Self::Discard>,
491✔
804
  ) -> Self::Item {
491✔
805
    item.leading_discards = leading_discards;
491✔
806
    item
491✔
807
  }
491✔
808

809
  fn new_vector_context(&self) -> Self::VectorContext {
24✔
810
    Vec::new()
24✔
811
  }
24✔
812

813
  fn new_list_context(&self) -> Self::ListContext {
43✔
814
    Vec::new()
43✔
815
  }
43✔
816

817
  fn new_map_context(&self) -> Self::MapContext {
30✔
818
    (Vec::new(), None)
30✔
819
  }
30✔
820

821
  fn new_set_context(&self) -> Self::SetContext {
7✔
822
    Vec::new()
7✔
823
  }
7✔
824

825
  fn add_to_vector(
86✔
826
    &self,
86✔
827
    ctx: &mut Self::VectorContext,
86✔
828
    parsed: Parsed<Self::Item>,
86✔
829
    leading_discards: Vec<Self::Discard>,
86✔
830
  ) -> Result<(), Error> {
86✔
831
    ctx.push(self.with_leading_discards(parsed.item, leading_discards));
86✔
832
    Ok(())
86✔
833
  }
86✔
834

835
  fn add_to_list(
91✔
836
    &self,
91✔
837
    ctx: &mut Self::ListContext,
91✔
838
    parsed: Parsed<Self::Item>,
91✔
839
    leading_discards: Vec<Self::Discard>,
91✔
840
  ) -> Result<(), Error> {
91✔
841
    ctx.push(self.with_leading_discards(parsed.item, leading_discards));
91✔
842
    Ok(())
91✔
843
  }
91✔
844

845
  fn add_to_map(
81✔
846
    &self,
81✔
847
    ctx: &mut Self::MapContext,
81✔
848
    parsed: Parsed<Self::Item>,
81✔
849
    leading_discards: Vec<Self::Discard>,
81✔
850
  ) -> Result<(), Error> {
81✔
851
    let parsed = self.with_leading_discards(parsed.item, leading_discards);
81✔
852
    if let Some(key) = ctx.1.take() {
81✔
853
      ctx.0.push((key, parsed));
40✔
854
    } else {
41✔
855
      ctx.1 = Some(parsed);
41✔
856
    }
41✔
857
    Ok(())
81✔
858
  }
81✔
859

860
  fn add_to_set(
25✔
861
    &self,
25✔
862
    ctx: &mut Self::SetContext,
25✔
863
    parsed: Parsed<Self::Item>,
25✔
864
    leading_discards: Vec<Self::Discard>,
25✔
865
  ) -> Result<(), Error> {
25✔
866
    ctx.push(self.with_leading_discards(parsed.item, leading_discards));
25✔
867
    Ok(())
25✔
868
  }
25✔
869

870
  fn finish_vector(
22✔
871
    &self,
22✔
872
    ctx: Self::VectorContext,
22✔
873
    trailing_discards: Vec<Self::Discard>,
22✔
874
    span: Span,
22✔
875
  ) -> Result<Parsed<Self::Item>, Error> {
22✔
876
    Ok(Parsed::new(Node::no_discards(NodeKind::Vector(ctx, trailing_discards), span), span))
22✔
877
  }
22✔
878

879
  fn finish_set(
7✔
880
    &self,
7✔
881
    ctx: Self::SetContext,
7✔
882
    trailing_discards: Vec<Self::Discard>,
7✔
883
    _validate: bool,
7✔
884
    span: Span,
7✔
885
  ) -> Result<Parsed<Self::Item>, Error> {
7✔
886
    Ok(Parsed::new(Node::no_discards(NodeKind::Set(ctx, trailing_discards), span), span))
7✔
887
  }
7✔
888

889
  fn finish_map(
30✔
890
    &self,
30✔
891
    ctx: Self::MapContext,
30✔
892
    trailing_discards: Vec<Self::Discard>,
30✔
893
    _validate: bool,
30✔
894
    close_pos: Position,
30✔
895
    span: Span,
30✔
896
  ) -> Result<Parsed<Self::Item>, Error> {
30✔
897
    if ctx.1.is_some() {
30✔
898
      return Err(Error::from_position(Code::UnexpectedEOF, close_pos));
1✔
899
    }
29✔
900
    Ok(Parsed::new(Node::no_discards(NodeKind::Map(ctx.0, trailing_discards), span), span))
29✔
901
  }
30✔
902

903
  fn finish_list(
39✔
904
    &self,
39✔
905
    ctx: Self::ListContext,
39✔
906
    trailing_discards: Vec<Self::Discard>,
39✔
907
    span: Span,
39✔
908
  ) -> Result<Parsed<Self::Item>, Error> {
39✔
909
    Ok(Parsed::new(Node::no_discards(NodeKind::List(ctx, trailing_discards), span), span))
39✔
910
  }
39✔
911

912
  fn tag(
21✔
913
    &self,
21✔
914
    tag: &'e str,
21✔
915
    tag_span: Span,
21✔
916
    value: Parsed<Self::Item>,
21✔
917
    leading_discards: Vec<Self::Discard>,
21✔
918
    span: Span,
21✔
919
  ) -> Parsed<Self::Item> {
21✔
920
    let value = self.with_leading_discards(value.item, leading_discards);
21✔
921
    Parsed::new(Node::no_discards(NodeKind::Tagged(tag, tag_span, Box::new(value)), span), span)
21✔
922
  }
21✔
923

924
  fn discard(
78✔
925
    &self,
78✔
926
    value: Parsed<Self::Item>,
78✔
927
    leading_discards: Vec<Self::Discard>,
78✔
928
    discard_span: Span,
78✔
929
  ) -> Self::Discard {
78✔
930
    Discard(self.with_leading_discards(value.item, leading_discards), discard_span)
78✔
931
  }
78✔
932
}
933

934
#[expect(clippy::mem_replace_with_default)]
935
const fn take_discards<D>(discards: &mut Vec<D>) -> Vec<D> {
1,721✔
936
  replace(discards, Vec::new())
1,721✔
937
}
1,721✔
938

939
#[inline]
940
fn add_to_context<'e, B: InternalParser<'e>>(
1,234✔
941
  context: &mut Option<&mut ParseContext<'e, B>>,
1,234✔
942
  builder: &B,
1,234✔
943
  parsed: Parsed<B::Item>,
1,234✔
944
) -> Result<(), Error> {
1,234✔
945
  match context.as_mut() {
1,234✔
946
    Some(ParseContext { kind: ContextKind::Vector(ctx, _), discards }) => {
288✔
947
      builder.add_to_vector(ctx, parsed, take_discards(discards))?;
288✔
948
    }
949
    Some(ParseContext { kind: ContextKind::List(ctx, _), discards }) => {
268✔
950
      builder.add_to_list(ctx, parsed, take_discards(discards))?;
268✔
951
    }
952
    Some(ParseContext { kind: ContextKind::Map(ctx, _), discards }) => {
573✔
953
      builder.add_to_map(ctx, parsed, take_discards(discards))?;
573✔
954
    }
955
    Some(ParseContext { kind: ContextKind::Set(ctx, _), discards }) => {
105✔
956
      builder.add_to_set(ctx, parsed, take_discards(discards))?;
105✔
957
    }
958
    _ => {}
×
959
  }
960
  Ok(())
1,234✔
961
}
1,234✔
962

963
#[inline]
964
fn handle_open_delimiter<'e, B: InternalParser<'e>>(
2,650✔
965
  walker: &mut Walker<'e, '_, B>,
2,650✔
966
  builder: &B,
2,650✔
967
  delim: OpenDelimiter,
2,650✔
968
) -> Result<(), Error> {
2,650✔
969
  let pos_start = walker.pos();
2,650✔
970
  match delim {
2,650✔
971
    OpenDelimiter::Vector => {
1,593✔
972
      let _ = walker.reader.nibble_next();
1,593✔
973
      walker.push_context(ParseContext::no_discards(ContextKind::Vector(
1,593✔
974
        builder.new_vector_context(),
1,593✔
975
        pos_start,
1,593✔
976
      )));
1,593✔
977
    }
1,593✔
978
    OpenDelimiter::List => {
602✔
979
      let _ = walker.reader.nibble_next();
602✔
980
      walker.push_context(ParseContext::no_discards(ContextKind::List(
602✔
981
        builder.new_list_context(),
602✔
982
        pos_start,
602✔
983
      )));
602✔
984
    }
602✔
985
    OpenDelimiter::Map => {
189✔
986
      let _ = walker.reader.nibble_next();
189✔
987
      walker.push_context(ParseContext::no_discards(ContextKind::Map(
189✔
988
        builder.new_map_context(),
189✔
989
        pos_start,
189✔
990
      )));
189✔
991
    }
189✔
992
    OpenDelimiter::Hash => {
993
      let _ = walker.reader.nibble_next();
266✔
994
      match walker.reader.peek_next() {
266✔
995
        Some('{') => {
37✔
996
          let _ = walker.reader.nibble_next();
37✔
997
          walker.push_context(ParseContext::no_discards(ContextKind::Set(
37✔
998
            builder.new_set_context(),
37✔
999
            pos_start,
37✔
1000
          )));
37✔
1001
        }
37✔
1002
        Some('_') => {
123✔
1003
          let _ = walker.reader.nibble_next();
123✔
1004
          walker.push_context(ParseContext::no_discards(ContextKind::Discard(pos_start)));
123✔
1005
        }
123✔
1006
        Some(c) if !c.is_whitespace() && !DELIMITERS.contains(&c) => {
106✔
1007
          let tag_pos_start = walker.pos();
100✔
1008
          let tag = walker.reader.slurp_tag()?;
100✔
1009
          let tag_span = walker.span_from(tag_pos_start);
96✔
1010
          if tag.is_empty() {
96✔
NEW
1011
            return Err(walker.make_error(Code::InvalidTag));
×
1012
          }
96✔
1013

1014
          walker.reader.nibble_whitespace();
96✔
1015
          walker
96✔
1016
            .push_context(ParseContext::no_discards(ContextKind::Tag(tag, tag_span, pos_start)));
96✔
1017
        }
1018
        Some(_) => return Err(walker.make_error(Code::InvalidTag)),
6✔
NEW
1019
        None => return Err(walker.make_error(Code::UnexpectedEOF)),
×
1020
      }
1021
    }
1022
  }
1023
  Ok(())
2,640✔
1024
}
2,650✔
1025

1026
fn wrap_pending_tags<'e, 'r, B: InternalParser<'e>>(
1,721✔
1027
  walker: &mut Walker<'e, 'r, B>,
1,721✔
1028
  builder: &B,
1,721✔
1029
  mut parsed: Parsed<B::Item>,
1,721✔
1030
) -> Parsed<B::Item> {
1,721✔
1031
  while matches!(walker.stack.last(), Some(ParseContext { kind: ContextKind::Tag(..), .. })) {
1,809✔
1032
    let ParseContext { kind: ContextKind::Tag(tag, tag_span, pos_start), discards } =
88✔
1033
      walker.pop_context().expect("tag context should exist")
88✔
1034
    else {
1035
      unreachable!("tag context should be on top of the stack");
×
1036
    };
1037

1038
    parsed = builder.tag(tag, tag_span, parsed, discards, walker.span_from(pos_start));
88✔
1039
  }
1040

1041
  parsed
1,721✔
1042
}
1,721✔
1043

1044
fn under_discard<'e, B: InternalParser<'e>>(walker: &Walker<'e, '_, B>) -> bool {
184✔
1045
  walker.stack.iter().any(|ctx| matches!(ctx.kind, ContextKind::Discard(..)))
304✔
1046
}
184✔
1047

1048
fn complete_value<'e, 'r, B: InternalParser<'e>>(
1,721✔
1049
  walker: &mut Walker<'e, 'r, B>,
1,721✔
1050
  builder: &B,
1,721✔
1051
  parsed: Parsed<B::Item>,
1,721✔
1052
) -> Result<Option<B::Item>, Error> {
1,721✔
1053
  let parsed = wrap_pending_tags(walker, builder, parsed);
1,721✔
1054

1055
  if walker.stack_len() == 1 {
1,721✔
1056
    let leading_discards =
370✔
1057
      take_discards(walker.last_context_discards().expect("Top should be there"));
370✔
1058
    return Ok(Some(builder.with_leading_discards(parsed.item, leading_discards)));
370✔
1059
  }
1,351✔
1060

1061
  if let Some(ParseContext { kind: ContextKind::Discard(pos_start), discards }) =
117✔
1062
    walker.stack.last_mut()
1,351✔
1063
  {
1064
    let pos_start = *pos_start;
117✔
1065
    let end_pos = parsed.span.1;
117✔
1066
    let discarded = builder.discard(parsed, take_discards(discards), Span(pos_start, end_pos));
117✔
1067
    walker.pop_context();
117✔
1068
    walker.stack.last_mut().expect("Top should be there").discards.push(discarded);
117✔
1069
    return Ok(None);
117✔
1070
  }
1,234✔
1071

1072
  add_to_context(&mut walker.stack.last_mut(), builder, parsed)?;
1,234✔
1073
  Ok(None)
1,234✔
1074
}
1,721✔
1075

1076
#[inline]
1077
fn handle_close_delimiter<'e, B: InternalParser<'e>>(
403✔
1078
  walker: &mut Walker<'e, '_, B>,
403✔
1079
  builder: &B,
403✔
1080
  delimiter: char,
403✔
1081
) -> Result<Option<B::Item>, Error> {
403✔
1082
  if walker.stack_len() <= 1 {
403✔
1083
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
2✔
1084
  }
401✔
1085

1086
  let expected = match walker.stack.last().expect("Len > 1 is never empty") {
401✔
1087
    ParseContext { kind: ContextKind::Vector(..), .. } => ']',
85✔
1088
    ParseContext { kind: ContextKind::List(..), .. } => ')',
122✔
1089
    ParseContext { kind: ContextKind::Map(..) | ContextKind::Set(..), .. } => '}',
190✔
1090
    _ => {
1091
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
4✔
1092
    }
1093
  };
1094

1095
  if delimiter != expected {
397✔
1096
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
7✔
1097
  }
390✔
1098

1099
  let parsed = match walker.pop_context() {
390✔
1100
    Some(ParseContext { kind: ContextKind::Vector(ctx, pos_start), discards }) => {
85✔
1101
      let _ = walker.reader.nibble_next();
85✔
1102
      builder.finish_vector(ctx, discards, walker.span_from(pos_start))?
85✔
1103
    }
1104
    Some(ParseContext { kind: ContextKind::List(ctx, pos_start), discards }) => {
121✔
1105
      let _ = walker.reader.nibble_next();
121✔
1106
      builder.finish_list(ctx, discards, walker.span_from(pos_start))?
121✔
1107
    }
1108
    Some(ParseContext { kind: ContextKind::Map(ctx, pos_start), discards }) => {
155✔
1109
      let validate = !under_discard(walker);
155✔
1110
      let close_pos = walker.pos();
155✔
1111
      let _ = walker.reader.nibble_next();
155✔
1112
      builder.finish_map(ctx, discards, validate, close_pos, walker.span_from(pos_start))?
155✔
1113
    }
1114
    Some(ParseContext { kind: ContextKind::Set(ctx, pos_start), discards }) => {
29✔
1115
      let validate = !under_discard(walker);
29✔
1116
      let _ = walker.reader.nibble_next();
29✔
1117
      builder.finish_set(ctx, discards, validate, walker.span_from(pos_start))?
29✔
1118
    }
1119
    _ => {
1120
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
×
1121
    }
1122
  };
1123

1124
  complete_value(walker, builder, parsed)
375✔
1125
}
403✔
1126

1127
fn parse_internal<'e, B: InternalParser<'e>>(
495✔
1128
  walker: &mut Walker<'e, '_, B>,
495✔
1129
  builder: &B,
495✔
1130
) -> Result<Option<B::Item>, Error> {
495✔
1131
  let mut result = None;
495✔
1132
  loop {
1133
    walker.reader.nibble_whitespace();
4,510✔
1134
    match walker.reader.peek_next() {
4,510✔
1135
      Some(';') => walker.reader.nibble_newline(),
24✔
1136
      Some('[') => handle_open_delimiter(walker, builder, OpenDelimiter::Vector)?,
1,593✔
1137
      Some('(') => handle_open_delimiter(walker, builder, OpenDelimiter::List)?,
602✔
1138
      Some('{') => handle_open_delimiter(walker, builder, OpenDelimiter::Map)?,
189✔
1139
      Some('#') => handle_open_delimiter(walker, builder, OpenDelimiter::Hash)?,
266✔
1140
      Some(d) if matches!(d, ']' | ')' | '}') => {
1,780✔
1141
        if let Some(parsed) = handle_close_delimiter(walker, builder, d)? {
403✔
1142
          result = Some(parsed);
190✔
1143
          break;
190✔
1144
        }
185✔
1145
      }
1146
      Some(c) => {
1,377✔
1147
        let pos_start = walker.reader.read_pos;
1,377✔
1148
        let atom = match c {
1,377✔
1149
          '\\' => parse_char(walker.reader.slurp_char()).map(Atom::Char),
143✔
1150
          '"' => Ok(Atom::Str(walker.reader.slurp_str()?)),
184✔
1151
          _ => edn_literal(walker.reader.slurp_literal()),
1,050✔
1152
        }
1153
        .map_err(|code| Error::from_position(code, pos_start))?;
1,365✔
1154
        let span = walker.reader.span_from(pos_start);
1,346✔
1155
        let parsed = Parsed::new(builder.atom(atom, span), span);
1,346✔
1156

1157
        if let Some(parsed) = complete_value(walker, builder, parsed)? {
1,346✔
1158
          result = Some(parsed);
180✔
1159
          break;
180✔
1160
        }
1,166✔
1161
      }
1162
      None => {
1163
        if walker.stack_len() > 1 {
56✔
1164
          return Err(walker.make_error(Code::UnexpectedEOF));
26✔
1165
        }
30✔
1166
        break;
30✔
1167
      }
1168
    }
1169
  }
1170
  Ok(result)
400✔
1171
}
495✔
1172

1173
#[inline]
1174
fn edn_literal(literal: &str) -> Result<Atom<'_>, Code> {
1,050✔
1175
  fn numeric(s: &str) -> bool {
692✔
1176
    let (first, second) = {
692✔
1177
      let mut s = s.chars();
692✔
1178
      (s.next(), s.next())
692✔
1179
    };
692✔
1180

1181
    let first = first.expect("Empty str is previously caught as nil");
692✔
1182
    if first.is_numeric() {
692✔
1183
      return true;
418✔
1184
    }
274✔
1185

1186
    if (first == '-' || first == '+')
274✔
1187
      && let Some(s) = second
84✔
1188
      && s.is_numeric()
79✔
1189
    {
1190
      return true;
55✔
1191
    }
219✔
1192

1193
    false
219✔
1194
  }
692✔
1195

1196
  Ok(match literal {
692✔
1197
    "nil" => Atom::Nil,
1,050✔
1198
    "true" => Atom::Bool(true),
1,031✔
1199
    "false" => Atom::Bool(false),
1,019✔
1200
    k if k.starts_with(':') => {
1,012✔
1201
      if k.len() <= 1 {
320✔
1202
        return Err(Code::InvalidKeyword);
2✔
1203
      }
318✔
1204
      Atom::Key(&k[1..])
318✔
1205
    }
1206
    n if numeric(n) => parse_number(n)?,
692✔
1207
    _ => Atom::Symbol(literal),
219✔
1208
  })
1209
}
1,050✔
1210

1211
#[inline]
1212
fn parse_char(lit: &str) -> Result<char, Code> {
143✔
1213
  let lit = &lit[1..]; // ignore the leading '\\'
143✔
1214
  match lit {
119✔
1215
    "newline" => Ok('\n'),
143✔
1216
    "return" => Ok('\r'),
137✔
1217
    "tab" => Ok('\t'),
131✔
1218
    "space" => Ok(' '),
125✔
1219
    c if c.len() == 1 => Ok(c.chars().next().expect("c must be len of 1")),
119✔
1220
    _ => Err(Code::InvalidChar),
2✔
1221
  }
1222
}
143✔
1223

1224
#[inline]
1225
fn parse_number(lit: &str) -> Result<Atom<'_>, Code> {
473✔
1226
  let mut chars = lit.chars().peekable();
473✔
1227
  let (number, radix, polarity) = {
469✔
1228
    let mut num_ptr_start = 0;
473✔
1229
    let polarity = chars.peek().map_or(1i8, |c| {
473✔
1230
      if *c == '-' {
473✔
1231
        num_ptr_start += 1;
47✔
1232
        -1i8
47✔
1233
      } else if *c == '+' {
426✔
1234
        // The EDN spec allows for a redundant '+' symbol, we just ignore it.
1235
        num_ptr_start += 1;
8✔
1236
        1i8
8✔
1237
      } else {
1238
        1i8
418✔
1239
      }
1240
    });
473✔
1241

1242
    let mut number = &lit[num_ptr_start..];
473✔
1243

1244
    if number.to_lowercase().starts_with("0x") {
473✔
1245
      number = &number[2..];
36✔
1246
      (number, 16, polarity)
36✔
1247
    } else if let Some(index) = number.to_lowercase().find('r') {
437✔
1248
      let radix = (number[0..index]).parse::<u8>();
26✔
1249

1250
      match radix {
26✔
1251
        Ok(r) => {
24✔
1252
          // from_str_radix panics if radix is not in the range from 2 to 36
1253
          if !(2..=36).contains(&r) {
24✔
1254
            return Err(Code::InvalidRadix(Some(r)));
2✔
1255
          }
22✔
1256

1257
          number = &number[(index + 1)..];
22✔
1258
          (number, r, polarity)
22✔
1259
        }
1260
        Err(_) => {
1261
          return Err(Code::InvalidRadix(None));
2✔
1262
        }
1263
      }
1264
    } else {
1265
      (number, 10, polarity)
411✔
1266
    }
1267
  };
1268

1269
  if let Ok(n) = i64::from_str_radix(number, radix.into()) {
469✔
1270
    return Ok(Atom::Int(n * i64::from(polarity)));
399✔
1271
  }
70✔
1272
  if radix == 10
70✔
1273
    && let Some(index) = number.find('/')
63✔
1274
  {
1275
    let (num, den) = number.split_at(index);
35✔
1276
    let num = num.parse::<i64>();
35✔
1277
    let den = den[1..].parse::<i64>();
35✔
1278

1279
    if let (Ok(n), Ok(d)) = (num, den) {
35✔
1280
      return Ok(Atom::Rational((n * i64::from(polarity), d)));
33✔
1281
    }
2✔
1282
  }
35✔
1283

1284
  #[cfg(feature = "arbitrary-nums")]
1285
  if let Some(n) = big_int_from_slice(number, radix, polarity) {
31✔
1286
    return Ok(Atom::BigInt(n));
8✔
1287
  }
23✔
1288
  #[cfg(feature = "floats")]
1289
  if radix == 10
23✔
1290
    && let Ok(n) = number.parse::<f64>()
20✔
1291
  {
1292
    return Ok(Atom::Double((n * f64::from(polarity)).into()));
10✔
1293
  }
13✔
1294
  #[cfg(feature = "arbitrary-nums")]
1295
  if let Some(n) = big_dec_from_slice(number, radix, polarity) {
13✔
1296
    return Ok(Atom::BigDec(n));
8✔
1297
  }
5✔
1298

1299
  Err(Code::InvalidNumber)
11✔
1300
}
473✔
1301

1302
#[inline]
1303
#[cfg(feature = "arbitrary-nums")]
1304
fn big_int_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<num_bigint::BigInt> {
31✔
1305
  // strip ending N, if it exists
1306
  let slice = slice.strip_suffix('N').map_or(slice, |slice| slice);
31✔
1307
  let num = num_bigint::BigInt::parse_bytes(slice.as_bytes(), radix.into())?;
31✔
1308
  Some(num * polarity)
8✔
1309
}
31✔
1310

1311
#[inline]
1312
#[cfg(feature = "arbitrary-nums")]
1313
fn big_dec_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<bigdecimal::BigDecimal> {
13✔
1314
  // strip ending M, if it exists
1315
  let slice = slice.strip_suffix('M').map_or(slice, |slice| slice);
13✔
1316
  let num = bigdecimal::BigDecimal::parse_bytes(slice.as_bytes(), radix.into())?;
13✔
1317
  Some(num * polarity)
8✔
1318
}
13✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc