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

Grinkers / clojure-reader / 20388528186

20 Dec 2025 03:33AM UTC coverage: 97.745% (+0.007%) from 97.738%
20388528186

push

github

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

201 of 203 new or added lines in 1 file covered. (99.01%)

997 of 1020 relevant lines covered (97.75%)

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

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

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

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

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

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

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

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

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

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

439
    false
122✔
440
  }
408✔
441

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

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

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

488
    let mut number = &lit[num_ptr_start..];
286✔
489

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

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

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

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

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

539
  Err(Code::InvalidNumber)
11✔
540
}
286✔
541

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

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

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

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

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