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

Grinkers / clojure-reader / 20388353195

20 Dec 2025 03:18AM UTC coverage: 97.738% (+1.0%) from 96.704%
20388353195

push

github

Grinkers
Switch to a stack based parser, instead of recursion based.

198 of 200 new or added lines in 1 file covered. (99.0%)

994 of 1017 relevant lines covered (97.74%)

220.93 hits per line

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

99.48
/src/parse.rs
1
#![expect(clippy::inline_always)]
2

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

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

11
pub fn parse(edn: &str) -> Result<(Edn<'_>, &str), Error> {
304✔
12
  let mut walker = Walker::new(edn);
304✔
13
  let internal_parse = parse_internal(&mut walker)?;
304✔
14
  internal_parse
228✔
15
    .map_or_else(|| Ok((Edn::Nil, &edn[walker.ptr..])), |ip| Ok((ip, &edn[walker.ptr..])))
228✔
16
}
304✔
17

18
const DELIMITERS: [char; 8] = [',', ']', '}', ')', ';', '(', '[', '{'];
19

20
#[derive(Debug)]
21
struct Walker<'e> {
22
  slice: &'e str,
23
  ptr: usize,
24
  column: usize,
25
  line: usize,
26
  stack: Vec<ParseContext<'e>>,
27
}
28

29
impl<'e> Walker<'e> {
30
  fn new(slice: &'e str) -> Self {
304✔
31
    Self { slice, ptr: 0, column: 1, line: 1, stack: alloc::vec![ParseContext::Top] }
304✔
32
  }
304✔
33
}
34

35
impl<'e> Walker<'e> {
36
  // Slurps until whitespace or delimiter, returning the slice.
37
  #[inline(always)]
38
  fn slurp_literal(&mut self) -> &'e str {
645✔
39
    let token = self.slice[self.ptr..]
645✔
40
      .split(|c: char| c.is_whitespace() || DELIMITERS.contains(&c) || c == '"')
4,439✔
41
      .next()
645✔
42
      .expect("Expected at least an empty slice");
645✔
43

44
    self.ptr += token.len();
645✔
45
    self.column += token.len();
645✔
46
    token
645✔
47
  }
645✔
48

49
  // Slurps a char. Special handling for chars that happen to be delimiters
50
  #[inline(always)]
51
  fn slurp_char(&mut self) -> &'e str {
78✔
52
    let starting_ptr = self.ptr;
78✔
53

54
    let mut ptr = 0;
78✔
55
    while let Some(c) = self.peek_next() {
308✔
56
      // first is always \\, second is always a char we want.
57
      // Handles edge cases of having a valid "\\[" but also "\\c[lolthisisvalidedn"
58
      if ptr > 1 && (c.is_whitespace() || DELIMITERS.contains(&c)) {
306✔
59
        break;
76✔
60
      }
230✔
61

62
      let _ = self.nibble_next();
230✔
63
      ptr += c.len_utf8();
230✔
64
    }
65
    &self.slice[starting_ptr..starting_ptr + ptr]
78✔
66
  }
78✔
67

68
  #[inline(always)]
69
  fn slurp_str(&mut self) -> Result<&'e str, Error> {
134✔
70
    let _ = self.nibble_next(); // Consume the leading '"' char
134✔
71
    let starting_ptr = self.ptr;
134✔
72
    let mut escape = false;
134✔
73
    loop {
74
      if let Some(c) = self.nibble_next() {
811✔
75
        if escape {
801✔
76
          match c {
4✔
77
            't' | 'r' | 'n' | '\\' | '\"' => (),
2✔
78
            _ => {
79
              return Err(Error {
2✔
80
                code: Code::InvalidEscape,
2✔
81
                column: Some(self.column),
2✔
82
                line: Some(self.line),
2✔
83
                ptr: Some(self.ptr),
2✔
84
              });
2✔
85
            }
86
          }
87
          escape = false;
2✔
88
        } else if c == '\"' {
797✔
89
          return Ok(&self.slice[starting_ptr..self.ptr - 1]);
122✔
90
        } else {
675✔
91
          escape = c == '\\';
675✔
92
        }
675✔
93
      } else {
94
        return Err(Error {
10✔
95
          code: Code::UnexpectedEOF,
10✔
96
          column: Some(self.column),
10✔
97
          line: Some(self.line),
10✔
98
          ptr: Some(self.ptr),
10✔
99
        });
10✔
100
      }
101
    }
102
  }
134✔
103

104
  #[inline(always)]
105
  fn slurp_tag(&mut self) -> Result<&'e str, Error> {
60✔
106
    self.nibble_whitespace();
60✔
107
    let starting_ptr = self.ptr;
60✔
108

109
    loop {
110
      if let Some(c) = self.peek_next() {
328✔
111
        if c.is_whitespace() || DELIMITERS.contains(&c) {
324✔
112
          return Ok(&self.slice[starting_ptr..self.ptr]);
56✔
113
        }
268✔
114
        let _ = self.nibble_next();
268✔
115
      } else {
116
        return Err(Error {
4✔
117
          code: Code::UnexpectedEOF,
4✔
118
          column: Some(self.column),
4✔
119
          line: Some(self.line),
4✔
120
          ptr: Some(self.ptr),
4✔
121
        });
4✔
122
      }
123
    }
124
  }
60✔
125

126
  // Nibbles away until the next new line
127
  #[inline(always)]
128
  fn nibble_newline(&mut self) {
16✔
129
    let len = self.slice[self.ptr..].split('\n').next().expect("Expected at least an empty slice");
16✔
130
    self.ptr += len.len();
16✔
131
    self.nibble_whitespace();
16✔
132
  }
16✔
133

134
  // Nibbles away until the start of the next form
135
  #[inline(always)]
136
  fn nibble_whitespace(&mut self) {
3,625✔
137
    while let Some(n) = self.peek_next() {
4,963✔
138
      if n == ',' || n.is_whitespace() {
4,931✔
139
        let _ = self.nibble_next();
1,338✔
140
        continue;
1,338✔
141
      }
3,593✔
142
      break;
3,593✔
143
    }
144
  }
3,625✔
145

146
  // Consumes next
147
  #[inline(always)]
148
  fn nibble_next(&mut self) -> Option<char> {
5,399✔
149
    let char = self.slice[self.ptr..].chars().next();
5,399✔
150
    if let Some(c) = char {
5,399✔
151
      self.ptr += c.len_utf8();
5,389✔
152
      if c == '\n' {
5,389✔
153
        self.line += 1;
94✔
154
        self.column = 1;
94✔
155
      } else {
5,295✔
156
        self.column += 1;
5,295✔
157
      }
5,295✔
158
    }
10✔
159
    char
5,399✔
160
  }
5,399✔
161

162
  // Peek into the next char
163
  #[inline(always)]
164
  fn peek_next(&self) -> Option<char> {
9,191✔
165
    self.slice[self.ptr..].chars().next()
9,191✔
166
  }
9,191✔
167

168
  #[inline(always)]
169
  fn push_context(&mut self, ctx: ParseContext<'e>) {
2,469✔
170
    self.stack.push(ctx);
2,469✔
171
  }
2,469✔
172

173
  #[inline(always)]
174
  fn pop_context(&mut self) -> Option<ParseContext<'e>> {
428✔
175
    self.stack.pop()
428✔
176
  }
428✔
177

178
  #[inline(always)]
179
  const fn stack_len(&self) -> usize {
1,484✔
180
    self.stack.len()
1,484✔
181
  }
1,484✔
182

183
  const fn make_error(&self, code: Code) -> Error {
41✔
184
    Error { code, line: Some(self.line), column: Some(self.column), ptr: Some(self.ptr) }
41✔
185
  }
41✔
186
}
187

188
#[derive(Debug, Clone, Copy)]
189
enum OpenDelimiter {
190
  Vector,
191
  List,
192
  Map,
193
  Hash,
194
}
195

196
#[derive(Debug, PartialEq, Eq)]
197
enum ParseContext<'e> {
198
  Top,
199
  Vector(Vec<Edn<'e>>),
200
  List(Vec<Edn<'e>>),
201
  Map(BTreeMap<Edn<'e>, Edn<'e>>, Option<Edn<'e>>),
202
  Set(BTreeSet<Edn<'e>>),
203
  Tag(&'e str),
204
  Discard,
205
}
206

207
#[inline]
208
fn parse_element<'e>(walker: &mut Walker<'e>, next: char) -> Result<Edn<'e>, Error> {
857✔
209
  let column_start = walker.column;
857✔
210
  let ptr_start = walker.ptr;
857✔
211
  let line_start = walker.line;
857✔
212
  match next {
857✔
213
    '\\' => match parse_char(walker.slurp_char()) {
78✔
214
      Ok(edn) => Ok(edn),
76✔
215
      Err(code) => Err(Error {
2✔
216
        code,
2✔
217
        line: Some(line_start),
2✔
218
        column: Some(column_start),
2✔
219
        ptr: Some(ptr_start),
2✔
220
      }),
2✔
221
    },
222
    '\"' => Ok(Edn::Str(walker.slurp_str()?)),
134✔
223
    _ => match edn_literal(walker.slurp_literal()) {
645✔
224
      Ok(edn) => Ok(edn),
628✔
225
      Err(code) => Err(Error {
17✔
226
        code,
17✔
227
        line: Some(line_start),
17✔
228
        column: Some(column_start),
17✔
229
        ptr: Some(ptr_start),
17✔
230
      }),
17✔
231
    },
232
  }
233
}
857✔
234

235
#[inline]
236
fn add_to_context<'e>(context: Option<&mut ParseContext<'e>>, edn: Edn<'e>) -> Result<(), Code> {
825✔
237
  match context {
825✔
238
    Some(ParseContext::Vector(vec)) | Some(ParseContext::List(vec)) => vec.push(edn),
345✔
239
    Some(ParseContext::Map(map, pending)) => {
418✔
240
      if let Some(key) = pending.take() {
418✔
241
        if map.insert(key, edn).is_some() {
204✔
242
          return Err(Code::HashMapDuplicateKey);
8✔
243
        }
196✔
244
      } else {
214✔
245
        *pending = Some(edn);
214✔
246
      }
214✔
247
    }
248
    Some(ParseContext::Set(set)) => {
62✔
249
      if !set.insert(edn) {
62✔
250
        return Err(Code::SetDuplicateKey);
4✔
251
      }
58✔
252
    }
NEW
253
    _ => {} // Do nothing. Errors will bubble up elsewhere.
×
254
  }
255
  Ok(())
813✔
256
}
825✔
257

258
#[inline]
259
fn handle_open_delimiter(walker: &mut Walker<'_>, delim: OpenDelimiter) -> Result<(), Error> {
2,346✔
260
  match delim {
2,346✔
261
    OpenDelimiter::Vector => {
1,554✔
262
      let _ = walker.nibble_next();
1,554✔
263
      walker.push_context(ParseContext::Vector(Vec::new()));
1,554✔
264
    }
1,554✔
265
    OpenDelimiter::List => {
552✔
266
      let _ = walker.nibble_next();
552✔
267
      walker.push_context(ParseContext::List(Vec::new()));
552✔
268
    }
552✔
269
    OpenDelimiter::Map => {
141✔
270
      let _ = walker.nibble_next();
141✔
271
      walker.push_context(ParseContext::Map(BTreeMap::new(), None));
141✔
272
    }
141✔
273
    OpenDelimiter::Hash => {
274
      let _ = walker.nibble_next();
99✔
275
      match walker.peek_next() {
99✔
276
        Some('{') => {
19✔
277
          let _ = walker.nibble_next();
19✔
278
          walker.push_context(ParseContext::Set(BTreeSet::new()));
19✔
279
        }
19✔
280
        Some('_') => {
20✔
281
          let _ = walker.nibble_next();
20✔
282
          walker.push_context(ParseContext::Discard);
20✔
283
        }
20✔
284
        _ => {
285
          let tag = walker.slurp_tag()?;
60✔
286
          walker.nibble_whitespace();
56✔
287
          walker.push_context(ParseContext::Tag(tag));
56✔
288
        }
289
      }
290
    }
291
  }
292
  Ok(())
2,342✔
293
}
2,346✔
294

295
#[inline]
296
fn handle_close_delimiter<'e>(
246✔
297
  walker: &mut Walker<'e>,
246✔
298
  delimiter: char,
246✔
299
) -> Result<Option<Edn<'e>>, Error> {
246✔
300
  if walker.stack_len() <= 1 {
246✔
301
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
2✔
302
  }
244✔
303
  let expected = match walker.stack.last().expect("Len > 1 is never empty") {
244✔
304
    ParseContext::Vector(_) => ']',
48✔
305
    ParseContext::List(_) => ')',
76✔
306
    ParseContext::Map(_, _) | ParseContext::Set(_) => '}',
118✔
307
    _ => {
308
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
2✔
309
    }
310
  };
311
  if delimiter != expected {
242✔
312
    return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
7✔
313
  }
235✔
314
  let mut edn = match walker.pop_context() {
235✔
315
    Some(ParseContext::Vector(vec)) => Edn::Vector(vec),
48✔
316
    Some(ParseContext::List(vec)) => Edn::List(vec),
75✔
317
    Some(ParseContext::Map(map, pending)) => {
101✔
318
      if pending.is_some() {
101✔
319
        return Err(walker.make_error(Code::UnexpectedEOF));
2✔
320
      }
99✔
321
      Edn::Map(map)
99✔
322
    }
323
    Some(ParseContext::Set(set)) => Edn::Set(set),
11✔
324
    _ => {
325
      // this should be impossible, due to checking for unmatched delimiters above
NEW
326
      return Err(walker.make_error(Code::UnmatchedDelimiter(delimiter)));
×
327
    }
328
  };
329
  let _ = walker.nibble_next();
233✔
330

331
  if walker.stack_len() == 1 {
233✔
332
    return Ok(Some(edn));
106✔
333
  }
127✔
334
  while let Some(context) = walker.pop_context() {
151✔
335
    match context {
151✔
336
      ParseContext::Tag(t) => {
24✔
337
        edn = Edn::Tagged(t, Box::new(edn));
24✔
338
      }
24✔
339
      other => {
127✔
340
        walker.push_context(other);
127✔
341
        break;
127✔
342
      }
343
    }
344
  }
345

346
  if walker.stack_len() == 1 {
127✔
347
    return Ok(Some(edn));
8✔
348
  } else if walker.stack.last() == Some(&ParseContext::Discard) {
119✔
349
    walker.pop_context();
2✔
350
  } else if let Err(code) = add_to_context(walker.stack.last_mut(), edn) {
117✔
351
    return Err(walker.make_error(code));
2✔
352
  }
115✔
353
  Ok(None)
117✔
354
}
246✔
355

356
#[inline]
357
fn handle_element<'e>(walker: &mut Walker<'e>, next: char) -> Result<Option<Edn<'e>>, Error> {
857✔
358
  let edn = parse_element(walker, next)?;
857✔
359
  if walker.stack_len() == 1 {
826✔
360
    return Ok(Some(edn));
87✔
361
  }
739✔
362
  let edn = match walker.stack.last() {
739✔
363
    Some(ParseContext::Tag(tag)) => {
24✔
364
      let tag = Edn::Tagged(tag, Box::new(edn));
24✔
365
      walker.pop_context();
24✔
366
      if walker.stack_len() == 1 {
24✔
367
        return Ok(Some(tag));
15✔
368
      }
9✔
369
      tag
9✔
370
    }
371
    Some(ParseContext::Discard) => {
372
      walker.pop_context();
16✔
373
      return Ok(None);
16✔
374
    }
375
    _ => edn,
699✔
376
  };
377
  if let Err(code) = add_to_context(walker.stack.last_mut(), edn) {
708✔
378
    return Err(walker.make_error(code));
10✔
379
  }
698✔
380
  Ok(None)
698✔
381
}
857✔
382

383
fn parse_internal<'e>(walker: &mut Walker<'e>) -> Result<Option<Edn<'e>>, Error> {
304✔
384
  let mut result: Option<Edn<'e>> = None;
304✔
385
  loop {
386
    walker.nibble_whitespace();
3,493✔
387
    match walker.peek_next() {
3,493✔
388
      Some(';') => walker.nibble_newline(),
16✔
389
      Some('[') => handle_open_delimiter(walker, OpenDelimiter::Vector)?,
1,554✔
390
      Some('(') => handle_open_delimiter(walker, OpenDelimiter::List)?,
552✔
391
      Some('{') => handle_open_delimiter(walker, OpenDelimiter::Map)?,
141✔
392
      Some('#') => handle_open_delimiter(walker, OpenDelimiter::Hash)?,
99✔
393
      Some(d) if matches!(d, ']' | ')' | '}') => {
1,103✔
394
        if let Some(edn) = handle_close_delimiter(walker, d)? {
246✔
395
          result = Some(edn);
114✔
396
          break;
114✔
397
        }
117✔
398
      }
399
      Some(c) => {
857✔
400
        if let Some(edn) = handle_element(walker, c)? {
857✔
401
          result = Some(edn);
102✔
402
          break;
102✔
403
        }
714✔
404
      }
405
      None => {
406
        if walker.stack_len() > 1 {
28✔
407
          return Err(walker.make_error(Code::UnexpectedEOF));
16✔
408
        }
12✔
409
        break;
12✔
410
      }
411
    }
412
  }
413
  Ok(result)
228✔
414
}
304✔
415

416
#[inline]
417
fn edn_literal(literal: &str) -> Result<Edn<'_>, Code> {
645✔
418
  fn numeric(s: &str) -> bool {
408✔
419
    let (first, second) = {
408✔
420
      let mut s = s.chars();
408✔
421
      (s.next(), s.next())
408✔
422
    };
408✔
423

424
    let first = first.expect("Empty str is previously caught as nil");
408✔
425
    if first.is_numeric() {
408✔
426
      return true;
250✔
427
    }
158✔
428

429
    if (first == '-' || first == '+')
158✔
430
      && let Some(s) = second
50✔
431
      && s.is_numeric()
48✔
432
    {
433
      return true;
36✔
434
    }
122✔
435

436
    false
122✔
437
  }
408✔
438

439
  Ok(match literal {
408✔
440
    "nil" => Edn::Nil,
645✔
441
    "true" => Edn::Bool(true),
637✔
442
    "false" => Edn::Bool(false),
631✔
443
    k if k.starts_with(':') => {
628✔
444
      if k.len() <= 1 {
220✔
445
        return Err(Code::InvalidKeyword);
2✔
446
      }
218✔
447
      Edn::Key(&k[1..])
218✔
448
    }
449
    n if numeric(n) => parse_number(n)?,
408✔
450
    _ => Edn::Symbol(literal),
122✔
451
  })
452
}
645✔
453

454
#[inline]
455
fn parse_char(lit: &str) -> Result<Edn<'_>, Code> {
78✔
456
  let lit = &lit[1..]; // ignore the leading '\\'
78✔
457
  match lit {
62✔
458
    "newline" => Ok(Edn::Char('\n')),
78✔
459
    "return" => Ok(Edn::Char('\r')),
74✔
460
    "tab" => Ok(Edn::Char('\t')),
70✔
461
    "space" => Ok(Edn::Char(' ')),
66✔
462
    c if c.len() == 1 => Ok(Edn::Char(c.chars().next().expect("c must be len of 1"))),
62✔
463
    _ => Err(Code::InvalidChar),
2✔
464
  }
465
}
78✔
466

467
#[inline]
468
fn parse_number(lit: &str) -> Result<Edn<'_>, Code> {
286✔
469
  let mut chars = lit.chars().peekable();
286✔
470
  let (number, radix, polarity) = {
282✔
471
    let mut num_ptr_start = 0;
286✔
472
    let polarity = chars.peek().map_or(1i8, |c| {
286✔
473
      if *c == '-' {
286✔
474
        num_ptr_start += 1;
32✔
475
        -1i8
32✔
476
      } else if *c == '+' {
254✔
477
        // The EDN spec allows for a redundant '+' symbol, we just ignore it.
478
        num_ptr_start += 1;
4✔
479
        1i8
4✔
480
      } else {
481
        1i8
250✔
482
      }
483
    });
286✔
484

485
    let mut number = &lit[num_ptr_start..];
286✔
486

487
    if number.to_lowercase().starts_with("0x") {
286✔
488
      number = &number[2..];
26✔
489
      (number, 16, polarity)
26✔
490
    } else if let Some(index) = number.to_lowercase().find('r') {
260✔
491
      let radix = (number[0..index]).parse::<u8>();
16✔
492

493
      match radix {
16✔
494
        Ok(r) => {
14✔
495
          // from_str_radix panics if radix is not in the range from 2 to 36
496
          if !(2..=36).contains(&r) {
14✔
497
            return Err(Code::InvalidRadix(Some(r)));
2✔
498
          }
12✔
499

500
          number = &number[(index + 1)..];
12✔
501
          (number, r, polarity)
12✔
502
        }
503
        Err(_) => {
504
          return Err(Code::InvalidRadix(None));
2✔
505
        }
506
      }
507
    } else {
508
      (number, 10, polarity)
244✔
509
    }
510
  };
511

512
  if let Ok(n) = i64::from_str_radix(number, radix.into()) {
282✔
513
    return Ok(Edn::Int(n * i64::from(polarity)));
232✔
514
  }
50✔
515
  if radix == 10
50✔
516
    && let Some((n, d)) = num_den_from_slice(number, polarity)
43✔
517
  {
518
    return Ok(Edn::Rational((n, d)));
19✔
519
  }
31✔
520

521
  #[cfg(feature = "arbitrary-nums")]
522
  if let Some(n) = big_int_from_slice(number, radix, polarity) {
25✔
523
    return Ok(Edn::BigInt(n));
5✔
524
  }
20✔
525
  #[cfg(feature = "floats")]
526
  if radix == 10
20✔
527
    && let Ok(n) = number.parse::<f64>()
17✔
528
  {
529
    return Ok(Edn::Double((n * f64::from(polarity)).into()));
8✔
530
  }
12✔
531
  #[cfg(feature = "arbitrary-nums")]
532
  if let Some(n) = big_dec_from_slice(number, radix, polarity) {
12✔
533
    return Ok(Edn::BigDec(n));
7✔
534
  }
5✔
535

536
  Err(Code::InvalidNumber)
11✔
537
}
286✔
538

539
#[inline]
540
#[cfg(feature = "arbitrary-nums")]
541
fn big_int_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<num_bigint::BigInt> {
25✔
542
  // strip ending N, if it exists
543
  let slice = slice.strip_suffix('N').map_or(slice, |slice| slice);
25✔
544
  let num = num_bigint::BigInt::parse_bytes(slice.as_bytes(), radix.into())?;
25✔
545
  Some(num * polarity)
5✔
546
}
25✔
547

548
#[inline]
549
#[cfg(feature = "arbitrary-nums")]
550
fn big_dec_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<bigdecimal::BigDecimal> {
12✔
551
  // strip ending M, if it exists
552
  let slice = slice.strip_suffix('M').map_or(slice, |slice| slice);
12✔
553
  let num = bigdecimal::BigDecimal::parse_bytes(slice.as_bytes(), radix.into())?;
12✔
554
  Some(num * polarity)
7✔
555
}
12✔
556

557
#[inline]
558
fn num_den_from_slice(slice: &str, polarity: i8) -> Option<(i64, i64)> {
43✔
559
  let index = slice.find('/');
43✔
560

561
  if let Some(i) = index {
43✔
562
    let (num, den) = slice.split_at(i);
21✔
563
    let num = num.parse::<i64>();
21✔
564
    let den = den[1..].parse::<i64>();
21✔
565

566
    if let (Ok(n), Ok(d)) = (num, den) {
21✔
567
      return Some((n * i64::from(polarity), d));
19✔
568
    }
2✔
569
  }
22✔
570
  None
24✔
571
}
43✔
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