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

Grinkers / clojure-reader / 23632796040

27 Mar 2026 05:34AM UTC coverage: 97.371% (-0.4%) from 97.754%
23632796040

push

github

web-flow
[Feature] Position-aware Parsing  (#7)

* Expose mod `parse` when flag 'unstable' is enabled

* Parsing of alternative position-aware syntax-tree `Node`

parse.rs:
- add structs `Position`, `Span`, `SourceReader`
- refactor out `line`, `column` & `ptr` fields of `Walker` to be
`SourceReader`'s responsibility, which sits as a mutable reference in
`Walker`'s field `reader`
- add enum `Node` which represents a concrete syntax-tree with span
information, and stores Maps and Sets as unprocessed lists
- modify  `parse` to parse a `Node` from a `SourceReader`
- remove `PartialEq` & `Eq` derives of `ParseContext` because they were
being used for a pattern-match (L448) that doesn't require it
- make `parse_element` work on `ParseReader` since it doesn't require
other `Walker` fields
- make `parse_char` directly return a char since it construct no other
`Node`/`Edn` variant, this makes it so we don't need to pass a `span`
parameter to it
- move `nibble_next()` call in `handle_close_delimiter` from after
matching over popped context, to be in each arm of that match (L404), so
as to let spans include closing delimiter, without moving it before the
`pop_context()` call for consistency

error.rs:
- method `Error::from_position` to construct an error directly from a
`parse::Position`

edn.rs:
- add `TryFrom` impl to convert a `Node` to an `Edn`, while processing
Maps and Sets (checks for duplicate keys and items respectively)
- update occurrences of `parse::parse` with parse::`parse_as_edn`

* Doc-comment spelling mistake

* clippy fix also results in full coverage

* README note + Allow `clippy::missing_errors_doc` when using the unstable feature.

* Add discarded forms to Node

- separated Node into NodeKind & Node
- add struct Discard containing a Node and also having its own span
- each Node may be preceded with any number of discarded nodes,
additionally collections (Vec, Map, Set, List) may contain any number of
trailing discarded nodes, which are stored in th... (continued)

272 of 278 new or added lines in 3 files covered. (97.84%)

1 existing line in 1 file now uncovered.

1111 of 1141 relevant lines covered (97.37%)

303.01 hits per line

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

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

4
use alloc::boxed::Box;
5
use alloc::vec::Vec;
6
use core::mem::replace;
7
use core::primitive::str;
8

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

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

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

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

64
/// Concrete EDN syntax-tree
65
///
66
/// Once [`parsed`](parse), it can then be converted to an [`Edn`] via [`TryFrom`](Edn::try_from)
67
///
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
// Node without discards
76
type SpannedNode<'e> = (NodeKind<'e>, Span);
77

78
impl Node<'_> {
79
  #[inline]
80
  pub const fn span(&self) -> Span {
489✔
81
    self.span
489✔
82
  }
489✔
83
}
84

85
impl<'e> Node<'e> {
86
  /// Construct a `node` with its kind and span but having no leading-discards
87
  pub const fn no_discards(kind: NodeKind<'e>, span: Span) -> Self {
412✔
88
    Self { kind, span, leading_discards: Vec::new() }
412✔
89
  }
412✔
90
}
91

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

147
/// Parses an [`Edn`] by first parsing a [`Node`], and then fallibly converting it
148
///
149
/// # Errors
150
///
151
/// See [`crate::error::Error`].
152
pub fn parse_as_edn(edn: &str) -> Result<(Edn<'_>, &str), Error> {
312✔
153
  let mut source_reader = SourceReader::new(edn);
312✔
154
  let parsed = parse(&mut source_reader)?;
312✔
155
  let span = parsed.span();
244✔
156
  Ok((parsed.try_into()?, &edn[span.1.ptr..]))
244✔
157
}
312✔
158

159
const DELIMITERS: [char; 8] = [',', ']', '}', ')', ';', '(', '[', '{'];
160

161
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
162
pub struct Position {
163
  pub line: usize,
164
  pub column: usize,
165
  pub ptr: usize,
166
}
167

168
impl Default for Position {
169
  fn default() -> Self {
411✔
170
    Self { line: 1, column: 1, ptr: 0 }
411✔
171
  }
411✔
172
}
173

174
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
175
pub struct Span(pub Position, pub Position);
176

177
impl Span {
178
  /// Whether the span is empty
179
  pub const fn is_empty(&self) -> bool {
2✔
180
    self.0.ptr == self.1.ptr
2✔
181
  }
2✔
182
}
183

184
/// A string-slice reader that records how much of the slice has been read
185
#[derive(Debug)]
186
pub struct SourceReader<'s> {
187
  slice: &'s str,
188
  // Position till where this string has been read
189
  read_pos: Position,
190
}
191

192
#[derive(Debug)]
193
struct Walker<'e, 'r> {
194
  reader: &'r mut SourceReader<'e>,
195
  stack: Vec<ParseContext<'e>>,
196
}
197

198
impl<'e> SourceReader<'e> {
199
  pub fn new(source: &'e str) -> Self {
411✔
200
    Self { slice: source, read_pos: Position::default() }
411✔
201
  }
411✔
202

203
  /// Span from some previously marked position to the current position of the reader
204
  #[inline(always)]
205
  pub const fn span_from(&self, marker: Position) -> Span {
1,215✔
206
    Span(marker, self.read_pos)
1,215✔
207
  }
1,215✔
208

209
  /// The portion of the source-string remaining to be read
210
  ///
211
  /// ```
212
  /// #[cfg(feature = "unstable")]
213
  /// {
214
  ///   use clojure_reader::parse::{SourceReader, parse};
215
  ///
216
  ///   let mut s = SourceReader::new("() []");
217
  ///   let _ = parse(&mut s).expect("failed to parse");
218
  ///   assert_eq!(s.remaining(), " []");
219
  /// }
220
  /// ```
221
  #[cfg_attr(not(feature = "unstable"), expect(dead_code))]
222
  // ^ Since this is private when `unstable` isn't enabled
NEW
223
  pub fn remaining(&self) -> &'e str {
×
NEW
224
    &self.slice[self.read_pos.ptr..]
×
NEW
225
  }
×
226

227
  /// Finishes the source-reader, returning:
228
  /// 1. the position one after the last read character, and
229
  /// 2. the original source-string.
230
  ///
231
  /// ```
232
  /// #[cfg(feature = "unstable")]
233
  /// {
234
  ///   use clojure_reader::parse::{Position, SourceReader, parse};
235
  ///
236
  ///   let mut s = SourceReader::new("() []");
237
  ///   let _ = parse(&mut s).expect("failed to parse");
238
  ///
239
  ///   let (pos, slice) = s.finish();
240
  ///   assert_eq!(
241
  ///       pos,
242
  ///       Position {
243
  ///           line: 1,
244
  ///           column: 3,
245
  ///           ptr: 2
246
  ///       }
247
  ///   );
248
  ///   assert_eq!(slice, "() []");
249
  ///   assert_eq!(&slice[pos.ptr..], " []");
250
  /// }
251
  /// ```
252
  #[cfg_attr(not(feature = "unstable"), expect(dead_code))]
253
  // ^ Since this is private when `unstable` isn't enabled
NEW
254
  pub const fn finish(self) -> (Position, &'e str) {
×
NEW
255
    (self.read_pos, self.slice)
×
UNCOV
256
  }
×
257

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

266
    self.read_pos.ptr += token.len();
894✔
267
    self.read_pos.column += token.chars().count();
894✔
268
    token
894✔
269
  }
894✔
270

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

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

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

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

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

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

333
  // Nibbles away until the next new line
334
  #[inline(always)]
335
  fn nibble_newline(&mut self) {
22✔
336
    let len =
22✔
337
      self.slice[self.read_pos.ptr..].split('\n').next().expect("Expected at least an empty slice");
22✔
338
    self.read_pos.ptr += len.len();
22✔
339
    self.nibble_whitespace();
22✔
340
  }
22✔
341

342
  // Nibbles away until the start of the next form
343
  #[inline(always)]
344
  fn nibble_whitespace(&mut self) {
4,380✔
345
    while let Some(n) = self.peek_next() {
6,284✔
346
      if n == ',' || n.is_whitespace() {
6,240✔
347
        let _ = self.nibble_next();
1,904✔
348
        continue;
1,904✔
349
      }
4,336✔
350
      break;
4,336✔
351
    }
352
  }
4,380✔
353

354
  // Consumes next
355
  #[inline(always)]
356
  fn nibble_next(&mut self) -> Option<char> {
7,102✔
357
    let char = self.slice[self.read_pos.ptr..].chars().next();
7,102✔
358
    if let Some(c) = char {
7,102✔
359
      self.read_pos.ptr += c.len_utf8();
7,092✔
360
      if c == '\n' {
7,092✔
361
        self.read_pos.line += 1;
136✔
362
        self.read_pos.column = 1;
136✔
363
      } else {
6,956✔
364
        self.read_pos.column += 1;
6,956✔
365
      }
6,956✔
366
    }
10✔
367
    char
7,102✔
368
  }
7,102✔
369

370
  // Peek into the next char
371
  #[inline(always)]
372
  fn peek_next(&self) -> Option<char> {
11,700✔
373
    self.slice[self.read_pos.ptr..].chars().next()
11,700✔
374
  }
11,700✔
375
}
376

377
impl<'e, 'r> Walker<'e, 'r> {
378
  fn new(reader: &'r mut SourceReader<'e>) -> Self {
425✔
379
    Self {
425✔
380
      reader,
425✔
381
      stack: alloc::vec![ParseContext { kind: ContextKind::Top, discards: Vec::new() }],
425✔
382
    }
425✔
383
  }
425✔
384

385
  #[inline(always)]
386
  const fn pos(&self) -> Position {
2,684✔
387
    self.reader.read_pos
2,684✔
388
  }
2,684✔
389

390
  /// Span from some previously marked position to the current position of the walker's reader
391
  #[inline(always)]
392
  const fn span_from(&self, marker: Position) -> Span {
594✔
393
    Span(marker, self.reader.read_pos)
594✔
394
  }
594✔
395

396
  #[inline(always)]
397
  fn push_context(&mut self, ctx: ParseContext<'e>) {
3,658✔
398
    self.stack.push(ctx);
3,658✔
399
  }
3,658✔
400

401
  #[inline(always)]
402
  fn pop_context(&mut self) -> Option<ParseContext<'e>> {
1,617✔
403
    self.stack.pop()
1,617✔
404
  }
1,617✔
405

406
  #[inline(always)]
407
  const fn stack_len(&self) -> usize {
2,119✔
408
    self.stack.len()
2,119✔
409
  }
2,119✔
410

411
  const fn make_error(&self, code: Code) -> Error {
37✔
412
    Error::from_position(code, self.pos())
37✔
413
  }
37✔
414

415
  fn last_context_discards(&mut self) -> Option<&mut Vec<Discard<'e>>> {
173✔
416
    match self.stack.last_mut() {
173✔
417
      Some(ParseContext { kind: _, discards }) => Some(discards),
173✔
NEW
418
      None => None,
×
419
    }
420
  }
173✔
421
}
422

423
#[derive(Debug, Clone, Copy)]
424
enum OpenDelimiter {
425
  Vector,
426
  List,
427
  Map,
428
  Hash,
429
}
430

431
// `Postion`, wherever present, contains the start position of that context
432
#[derive(Debug)]
433
enum ContextKind<'e> {
434
  Top,
435
  Vector(Vec<Node<'e>>, Position),
436
  List(Vec<Node<'e>>, Position),
437
  Map(Vec<(Node<'e>, Node<'e>)>, Option<Node<'e>>, Position),
438
  Set(Vec<Node<'e>>, Position),
439
  Tag(&'e str, /* Span of the tag string */ Span, Position),
440
  Discard(Position),
441
}
442

443
#[derive(Debug)]
444
struct ParseContext<'e> {
445
  kind: ContextKind<'e>,
446
  discards: Vec<Discard<'e>>,
447
}
448

449
impl<'e> ParseContext<'e> {
450
  const fn no_discards(kind: ContextKind<'e>) -> Self {
2,551✔
451
    Self { kind, discards: Vec::new() }
2,551✔
452
  }
2,551✔
453
}
454

455
#[inline]
456
fn parse_element<'e>(reader: &mut SourceReader<'e>, next: char) -> Result<SpannedNode<'e>, Error> {
1,211✔
457
  let pos_start = reader.read_pos;
1,211✔
458
  match next {
1,211✔
459
    '\\' => match parse_char(reader.slurp_char()) {
143✔
460
      Ok(node) => Ok((NodeKind::Char(node), reader.span_from(pos_start))),
141✔
461
      Err(code) => Err(Error::from_position(code, pos_start)),
2✔
462
    },
463
    '\"' => {
464
      let str = reader.slurp_str()?;
174✔
465
      Ok((NodeKind::Str(str), reader.span_from(pos_start)))
162✔
466
    }
467
    _ => {
468
      let lit = reader.slurp_literal();
894✔
469
      match edn_literal(lit, reader.span_from(pos_start)) {
894✔
470
        Ok(node) => Ok(node),
877✔
471
        Err(code) => Err(Error::from_position(code, pos_start)),
17✔
472
      }
473
    }
474
  }
475
}
1,211✔
476

477
#[expect(clippy::mem_replace_with_default)]
478
const fn take_discards<'e>(discards: &mut Vec<Discard<'e>>) -> Vec<Discard<'e>> {
1,557✔
479
  replace(discards, Vec::new())
1,557✔
480
}
1,557✔
481

482
#[inline]
483
fn add_to_context<'e>(context: &mut Option<&mut ParseContext<'e>>, (kind, span): SpannedNode<'e>) {
1,087✔
484
  match context.as_mut() {
1,087✔
485
    Some(ParseContext {
486
      kind: ContextKind::Vector(vec, _) | ContextKind::List(vec, _) | ContextKind::Set(vec, _),
261✔
487
      discards,
261✔
488
    }) => {
597✔
489
      let leading_discards = take_discards(discards);
597✔
490
      vec.push(Node { kind, span, leading_discards });
597✔
491
    }
597✔
492
    Some(ParseContext { kind: ContextKind::Map(map, pending, _), discards }) => {
490✔
493
      let leading_discards = take_discards(discards);
490✔
494
      let node = Node { kind, span, leading_discards };
490✔
495
      if let Some(key) = pending.take() {
490✔
496
        map.push((key, node));
240✔
497
      } else {
250✔
498
        *pending = Some(node);
250✔
499
      }
250✔
500
    }
501
    _ => {} // Do nothing. Errors will bubble up elsewhere.
×
502
  }
503
}
1,087✔
504

505
#[inline]
506
fn handle_open_delimiter(walker: &mut Walker<'_, '_>, delim: OpenDelimiter) -> Result<(), Error> {
2,555✔
507
  let pos_start = walker.pos();
2,555✔
508
  match delim {
2,555✔
509
    OpenDelimiter::Vector => {
1,575✔
510
      let _ = walker.reader.nibble_next();
1,575✔
511
      walker.push_context(ParseContext::no_discards(ContextKind::Vector(Vec::new(), pos_start)));
1,575✔
512
    }
1,575✔
513
    OpenDelimiter::List => {
594✔
514
      let _ = walker.reader.nibble_next();
594✔
515
      walker.push_context(ParseContext::no_discards(ContextKind::List(Vec::new(), pos_start)));
594✔
516
    }
594✔
517
    OpenDelimiter::Map => {
169✔
518
      let _ = walker.reader.nibble_next();
169✔
519
      walker.push_context(ParseContext::no_discards(ContextKind::Map(Vec::new(), None, pos_start)));
169✔
520
    }
169✔
521
    OpenDelimiter::Hash => {
522
      let _ = walker.reader.nibble_next();
217✔
523
      match walker.reader.peek_next() {
217✔
524
        Some('{') => {
26✔
525
          let _ = walker.reader.nibble_next();
26✔
526
          walker.push_context(ParseContext::no_discards(ContextKind::Set(Vec::new(), pos_start)));
26✔
527
        }
26✔
528
        Some('_') => {
99✔
529
          let _ = walker.reader.nibble_next();
99✔
530
          walker.push_context(ParseContext::no_discards(ContextKind::Discard(pos_start)));
99✔
531
        }
99✔
532
        _ => {
533
          let tag_pos_start = walker.pos();
92✔
534
          let tag = walker.reader.slurp_tag()?;
92✔
535
          let tag_span = walker.span_from(tag_pos_start);
88✔
536

537
          walker.reader.nibble_whitespace();
88✔
538
          walker
88✔
539
            .push_context(ParseContext::no_discards(ContextKind::Tag(tag, tag_span, pos_start)));
88✔
540
        }
541
      }
542
    }
543
  }
544
  Ok(())
2,551✔
545
}
2,555✔
546

547
#[inline]
548
fn handle_close_delimiter<'e>(
350✔
549
  walker: &mut Walker<'e, '_>,
350✔
550
  delimiter: char,
350✔
551
) -> Result<Option<(NodeKind<'e>, Span)>, Error> {
350✔
552
  if walker.stack_len() <= 1 {
350✔
553
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
2✔
554
  }
348✔
555
  let expected = match walker.stack.last().expect("Len > 1 is never empty") {
348✔
556
    ParseContext { kind: ContextKind::Vector(..), .. } => ']',
67✔
557
    ParseContext { kind: ContextKind::List(..), .. } => ')',
114✔
558
    ParseContext { kind: ContextKind::Map(..) | ContextKind::Set(..), .. } => '}',
163✔
559
    _ => {
560
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
4✔
561
    }
562
  };
563
  if delimiter != expected {
344✔
564
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
7✔
565
  }
337✔
566
  let (mut node, mut span) = match walker.pop_context() {
337✔
567
    Some(ParseContext { kind: ContextKind::Vector(vec, pos_start), discards }) => {
67✔
568
      let _ = walker.reader.nibble_next();
67✔
569
      (NodeKind::Vector(vec, discards), walker.span_from(pos_start))
67✔
570
    }
571
    Some(ParseContext { kind: ContextKind::List(vec, pos_start), discards }) => {
113✔
572
      let _ = walker.reader.nibble_next();
113✔
573
      (NodeKind::List(vec, discards), walker.span_from(pos_start))
113✔
574
    }
575
    Some(ParseContext { kind: ContextKind::Map(map, pending, pos_start), discards }) => {
137✔
576
      if pending.is_some() {
137✔
577
        return Err(walker.make_error(Code::UnexpectedEOF));
2✔
578
      }
135✔
579
      let _ = walker.reader.nibble_next();
135✔
580
      (NodeKind::Map(map, discards), walker.span_from(pos_start))
135✔
581
    }
582
    Some(ParseContext { kind: ContextKind::Set(set, pos_start), discards }) => {
20✔
583
      let _ = walker.reader.nibble_next();
20✔
584
      (NodeKind::Set(set, discards), walker.span_from(pos_start))
20✔
585
    }
586
    _ => {
587
      // this should be impossible, due to checking for unmatched delimiters above
588
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
×
589
    }
590
  };
591

592
  if walker.stack_len() == 1 {
335✔
593
    return Ok(Some((node, span)));
161✔
594
  }
174✔
595
  while let Some(context) = walker.pop_context() {
210✔
596
    match context {
210✔
597
      ParseContext { kind: ContextKind::Tag(t, t_span, pos_start), discards } => {
36✔
598
        node = NodeKind::Tagged(
36✔
599
          t,
36✔
600
          t_span,
36✔
601
          Box::new(Node { kind: node, span, leading_discards: discards }),
36✔
602
        );
36✔
603
        span = walker.span_from(pos_start);
36✔
604
      }
36✔
605
      other => {
174✔
606
        walker.push_context(other); // If Top was popped, it gets reinserted here
174✔
607
        break;
174✔
608
      }
609
    }
610
  }
611

612
  if walker.stack_len() == 1 {
174✔
613
    return Ok(Some((node, span)));
12✔
614
  } else if let Some(ParseContext { kind: ContextKind::Discard(pos_start), discards }) =
8✔
615
    walker.stack.last_mut()
162✔
616
  {
8✔
617
    let pos_start = *pos_start;
8✔
618
    let leading_discards = take_discards(discards);
8✔
619
    let discarded =
8✔
620
      Discard(Node { kind: node, span, leading_discards }, walker.span_from(pos_start));
8✔
621
    walker.pop_context(); // Popped context is not Top, so walker.stack still has len >= 1
8✔
622
    walker.stack.last_mut().expect("Top should be there").discards.push(discarded);
8✔
623
  } else {
154✔
624
    add_to_context(&mut walker.stack.last_mut(), (node, span));
154✔
625
  }
154✔
626
  Ok(None)
162✔
627
}
350✔
628

629
#[inline]
630
fn handle_element<'e>(walker: &mut Walker<'e, '_>, next: char) -> Result<Option<Node<'e>>, Error> {
1,211✔
631
  let (node, span) = parse_element(walker.reader, next)?;
1,211✔
632
  if walker.stack_len() == 1 {
1,180✔
633
    let last_ctx = walker.stack.last_mut().expect("stack_len() == 1");
137✔
634
    let node = Node { kind: node, span, leading_discards: take_discards(&mut last_ctx.discards) };
137✔
635
    return Ok(Some(node));
137✔
636
  }
1,043✔
637
  let (node, span) = match walker.stack.last_mut() {
1,043✔
638
    Some(ParseContext { kind: ContextKind::Tag(tag, tag_span, pos_start), discards }) => {
40✔
639
      let pos_start = *pos_start;
40✔
640
      let leading_discards = take_discards(discards);
40✔
641
      let mut node =
40✔
642
        NodeKind::Tagged(tag, *tag_span, Box::new(Node { kind: node, span, leading_discards }));
40✔
643
      let mut span = walker.span_from(pos_start);
40✔
644

645
      walker.pop_context();
40✔
646
      while let Some(ParseContext { kind: ContextKind::Tag(t, t_span, pos_start), discards }) =
4✔
647
        walker.stack.last_mut()
44✔
648
      {
4✔
649
        let pos_start = *pos_start;
4✔
650
        let leading_discards = take_discards(discards);
4✔
651
        node = NodeKind::Tagged(t, *t_span, Box::new(Node { kind: node, span, leading_discards }));
4✔
652
        span = walker.span_from(pos_start);
4✔
653
        walker.pop_context();
4✔
654
      }
4✔
655
      if walker.stack_len() == 1 {
40✔
656
        let last_ctx = walker.stack.last_mut().expect("stack_len() == 1");
25✔
657
        let node =
25✔
658
          Node { kind: node, span, leading_discards: take_discards(&mut last_ctx.discards) };
25✔
659
        return Ok(Some(node));
25✔
660
      }
15✔
661
      (node, span)
15✔
662
    }
663
    Some(ParseContext { kind: ContextKind::Discard(pos_start), discards }) => {
83✔
664
      let pos_start = *pos_start;
83✔
665
      let leading_discards = take_discards(discards);
83✔
666
      let discarded =
83✔
667
        Discard(Node { kind: node, span, leading_discards }, walker.span_from(pos_start));
83✔
668
      walker.pop_context(); // Popped context is not Top, so walker.stack still has len >= 1
83✔
669
      walker.stack.last_mut().expect("Top should be there").discards.push(discarded);
83✔
670
      return Ok(None);
83✔
671
    }
672
    _ => (node, span),
920✔
673
  };
674

675
  match walker.pop_context().expect("Top should be there") {
935✔
676
    // Adding to a discard context discards that element & ends the context
677
    ParseContext { kind: ContextKind::Discard(pos_start), discards } => {
2✔
678
      let discarded =
2✔
679
        Discard(Node { kind: node, span, leading_discards: discards }, Span(pos_start, span.1));
2✔
680
      walker.stack.last_mut().expect("Top should be there").discards.push(discarded);
2✔
681
    }
2✔
682
    ctx => {
933✔
683
      walker.push_context(ctx);
933✔
684
      add_to_context(&mut walker.stack.last_mut(), (node, span));
933✔
685
    }
933✔
686
  }
687
  Ok(None)
935✔
688
}
1,211✔
689

690
fn parse_internal<'e>(walker: &mut Walker<'e, '_>) -> Result<Option<Node<'e>>, Error> {
425✔
691
  let mut result: Option<Node<'e>> = None;
425✔
692
  loop {
693
    walker.reader.nibble_whitespace();
4,178✔
694
    match walker.reader.peek_next() {
4,178✔
695
      Some(';') => walker.reader.nibble_newline(),
22✔
696
      Some('[') => handle_open_delimiter(walker, OpenDelimiter::Vector)?,
1,575✔
697
      Some('(') => handle_open_delimiter(walker, OpenDelimiter::List)?,
594✔
698
      Some('{') => handle_open_delimiter(walker, OpenDelimiter::Map)?,
169✔
699
      Some('#') => handle_open_delimiter(walker, OpenDelimiter::Hash)?,
217✔
700
      Some(d) if matches!(d, ']' | ')' | '}') => {
1,561✔
701
        if let Some((kind, span)) = handle_close_delimiter(walker, d)? {
350✔
702
          result = Some(Node {
173✔
703
            kind,
173✔
704
            span,
173✔
705
            leading_discards: take_discards(
173✔
706
              // `handle_close_delimiter` never pops Top, so walker.stack_len() >= 1
173✔
707
              walker.last_context_discards().expect("Top should be there"),
173✔
708
            ),
173✔
709
          });
173✔
710
          break;
173✔
711
        }
162✔
712
      }
713
      Some(c) => {
1,211✔
714
        if let Some(node) = handle_element(walker, c)? {
1,211✔
715
          result = Some(node);
162✔
716
          break;
162✔
717
        }
1,018✔
718
      }
719
      None => {
720
        if walker.stack_len() > 1 {
40✔
721
          return Err(walker.make_error(Code::UnexpectedEOF));
22✔
722
        }
18✔
723
        break;
18✔
724
      }
725
    }
726
  }
727
  Ok(result)
353✔
728
}
425✔
729

730
#[inline]
731
fn edn_literal(literal: &str, span: Span) -> Result<SpannedNode<'_>, Code> {
894✔
732
  fn numeric(s: &str) -> bool {
590✔
733
    let (first, second) = {
590✔
734
      let mut s = s.chars();
590✔
735
      (s.next(), s.next())
590✔
736
    };
590✔
737

738
    let first = first.expect("Empty str is previously caught as nil");
590✔
739
    if first.is_numeric() {
590✔
740
      return true;
320✔
741
    }
270✔
742

743
    if (first == '-' || first == '+')
270✔
744
      && let Some(s) = second
81✔
745
      && s.is_numeric()
76✔
746
    {
747
      return true;
52✔
748
    }
218✔
749

750
    false
218✔
751
  }
590✔
752

753
  Ok(match literal {
590✔
754
    "nil" => (NodeKind::Nil, span),
894✔
755
    "true" => (NodeKind::Bool(true), span),
881✔
756
    "false" => (NodeKind::Bool(false), span),
871✔
757
    k if k.starts_with(':') => {
867✔
758
      if k.len() <= 1 {
277✔
759
        return Err(Code::InvalidKeyword);
2✔
760
      }
275✔
761
      (NodeKind::Key(&k[1..]), span)
275✔
762
    }
763
    n if numeric(n) => parse_number(n, span)?,
590✔
764
    _ => (NodeKind::Symbol(literal), span),
218✔
765
  })
766
}
894✔
767

768
#[inline]
769
fn parse_char(lit: &str) -> Result<char, Code> {
143✔
770
  let lit = &lit[1..]; // ignore the leading '\\'
143✔
771
  match lit {
119✔
772
    "newline" => Ok('\n'),
143✔
773
    "return" => Ok('\r'),
137✔
774
    "tab" => Ok('\t'),
131✔
775
    "space" => Ok(' '),
125✔
776
    c if c.len() == 1 => Ok(c.chars().next().expect("c must be len of 1")),
119✔
777
    _ => Err(Code::InvalidChar),
2✔
778
  }
779
}
143✔
780

781
#[inline]
782
fn parse_number(lit: &str, span: Span) -> Result<SpannedNode<'_>, Code> {
372✔
783
  let mut chars = lit.chars().peekable();
372✔
784
  let (number, radix, polarity) = {
368✔
785
    let mut num_ptr_start = 0;
372✔
786
    let polarity = chars.peek().map_or(1i8, |c| {
372✔
787
      if *c == '-' {
372✔
788
        num_ptr_start += 1;
44✔
789
        -1i8
44✔
790
      } else if *c == '+' {
328✔
791
        // The EDN spec allows for a redundant '+' symbol, we just ignore it.
792
        num_ptr_start += 1;
8✔
793
        1i8
8✔
794
      } else {
795
        1i8
320✔
796
      }
797
    });
372✔
798

799
    let mut number = &lit[num_ptr_start..];
372✔
800

801
    if number.to_lowercase().starts_with("0x") {
372✔
802
      number = &number[2..];
34✔
803
      (number, 16, polarity)
34✔
804
    } else if let Some(index) = number.to_lowercase().find('r') {
338✔
805
      let radix = (number[0..index]).parse::<u8>();
26✔
806

807
      match radix {
26✔
808
        Ok(r) => {
24✔
809
          // from_str_radix panics if radix is not in the range from 2 to 36
810
          if !(2..=36).contains(&r) {
24✔
811
            return Err(Code::InvalidRadix(Some(r)));
2✔
812
          }
22✔
813

814
          number = &number[(index + 1)..];
22✔
815
          (number, r, polarity)
22✔
816
        }
817
        Err(_) => {
818
          return Err(Code::InvalidRadix(None));
2✔
819
        }
820
      }
821
    } else {
822
      (number, 10, polarity)
312✔
823
    }
824
  };
825

826
  if let Ok(n) = i64::from_str_radix(number, radix.into()) {
368✔
827
    return Ok((NodeKind::Int(n * i64::from(polarity)), span));
304✔
828
  }
64✔
829
  if radix == 10
64✔
830
    && let Some((n, d)) = num_den_from_slice(number, polarity)
57✔
831
  {
832
    return Ok((NodeKind::Rational((n, d)), span));
31✔
833
  }
33✔
834

835
  #[cfg(feature = "arbitrary-nums")]
836
  if let Some(n) = big_int_from_slice(number, radix, polarity) {
27✔
837
    return Ok((NodeKind::BigInt(n), span));
5✔
838
  }
22✔
839
  #[cfg(feature = "floats")]
840
  if radix == 10
22✔
841
    && let Ok(n) = number.parse::<f64>()
19✔
842
  {
843
    return Ok((NodeKind::Double((n * f64::from(polarity)).into()), span));
10✔
844
  }
12✔
845
  #[cfg(feature = "arbitrary-nums")]
846
  if let Some(n) = big_dec_from_slice(number, radix, polarity) {
12✔
847
    return Ok((NodeKind::BigDec(n), span));
7✔
848
  }
5✔
849

850
  Err(Code::InvalidNumber)
11✔
851
}
372✔
852

853
#[inline]
854
#[cfg(feature = "arbitrary-nums")]
855
fn big_int_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<num_bigint::BigInt> {
27✔
856
  // strip ending N, if it exists
857
  let slice = slice.strip_suffix('N').map_or(slice, |slice| slice);
27✔
858
  let num = num_bigint::BigInt::parse_bytes(slice.as_bytes(), radix.into())?;
27✔
859
  Some(num * polarity)
5✔
860
}
27✔
861

862
#[inline]
863
#[cfg(feature = "arbitrary-nums")]
864
fn big_dec_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<bigdecimal::BigDecimal> {
12✔
865
  // strip ending M, if it exists
866
  let slice = slice.strip_suffix('M').map_or(slice, |slice| slice);
12✔
867
  let num = bigdecimal::BigDecimal::parse_bytes(slice.as_bytes(), radix.into())?;
12✔
868
  Some(num * polarity)
7✔
869
}
12✔
870

871
#[inline]
872
fn num_den_from_slice(slice: &str, polarity: i8) -> Option<(i64, i64)> {
57✔
873
  let index = slice.find('/');
57✔
874

875
  if let Some(i) = index {
57✔
876
    let (num, den) = slice.split_at(i);
33✔
877
    let num = num.parse::<i64>();
33✔
878
    let den = den[1..].parse::<i64>();
33✔
879

880
    if let (Ok(n), Ok(d)) = (num, den) {
33✔
881
      return Some((n * i64::from(polarity), d));
31✔
882
    }
2✔
883
  }
24✔
884
  None
26✔
885
}
57✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc