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

Grinkers / clojure-reader / 16545951911

27 Jul 2025 01:40AM UTC coverage: 97.732% (-0.009%) from 97.741%
16545951911

push

github

Grinkers
0.4.0 release.

991 of 1014 relevant lines covered (97.73%)

121.08 hits per line

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

99.47
/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
const DELIMITERS: [char; 8] = [',', ']', '}', ')', ';', '(', '[', '{'];
12

13
#[derive(Debug)]
14
struct Walker {
15
  ptr: usize,
16
  column: usize,
17
  line: usize,
18
}
19

20
impl Default for Walker {
21
  fn default() -> Self {
278✔
22
    Self { ptr: 0, column: 1, line: 1 }
278✔
23
  }
278✔
24
}
25

26
impl Walker {
27
  // Slurps until whitespace or delimiter, returning the slice.
28
  #[inline(always)]
29
  fn slurp_literal<'w>(&mut self, slice: &'w str) -> &'w str {
578✔
30
    let token = slice[self.ptr..]
578✔
31
      .split(|c: char| c.is_whitespace() || DELIMITERS.contains(&c) || c == '"')
4,130✔
32
      .next()
578✔
33
      .expect("Expected at least an empty slice");
578✔
34

35
    self.ptr += token.len();
578✔
36
    self.column += token.len();
578✔
37
    token
578✔
38
  }
578✔
39

40
  // Slurps a char. Special handling for chars that happen to be delimiters
41
  #[inline(always)]
42
  fn slurp_char<'a>(&mut self, slice: &'a str) -> &'a str {
78✔
43
    let starting_ptr = self.ptr;
78✔
44

45
    let mut ptr = 0;
78✔
46
    while let Some(c) = self.peek_next(slice) {
308✔
47
      // first is always \\, second is always a char we want.
48
      // Handles edge cases of having a valid "\\[" but also "\\c[lolthisisvalidedn"
49
      if ptr > 1 && (c.is_whitespace() || DELIMITERS.contains(&c)) {
306✔
50
        break;
76✔
51
      }
230✔
52

53
      let _ = self.nibble_next(slice);
230✔
54
      ptr += c.len_utf8();
230✔
55
    }
56
    &slice[starting_ptr..starting_ptr + ptr]
78✔
57
  }
78✔
58

59
  #[inline(always)]
60
  fn slurp_str<'w>(&mut self, slice: &'w str) -> Result<&'w str, Error> {
122✔
61
    let _ = self.nibble_next(slice); // Consume the leading '"' char
122✔
62
    let starting_ptr = self.ptr;
122✔
63
    let mut escape = false;
122✔
64
    loop {
65
      if let Some(c) = self.nibble_next(slice) {
759✔
66
        if escape {
749✔
67
          match c {
4✔
68
            't' | 'r' | 'n' | '\\' | '\"' => (),
2✔
69
            _ => {
70
              return Err(Error {
2✔
71
                code: Code::InvalidEscape,
2✔
72
                column: Some(self.column),
2✔
73
                line: Some(self.line),
2✔
74
                ptr: Some(self.ptr),
2✔
75
              });
2✔
76
            }
77
          }
78
          escape = false;
2✔
79
        } else if c == '\"' {
745✔
80
          return Ok(&slice[starting_ptr..self.ptr - 1]);
110✔
81
        } else {
635✔
82
          escape = c == '\\';
635✔
83
        }
635✔
84
      } else {
85
        return Err(Error {
10✔
86
          code: Code::UnexpectedEOF,
10✔
87
          column: Some(self.column),
10✔
88
          line: Some(self.line),
10✔
89
          ptr: Some(self.ptr),
10✔
90
        });
10✔
91
      }
92
    }
93
  }
122✔
94

95
  #[inline(always)]
96
  fn slurp_tag<'w>(&mut self, slice: &'w str) -> Result<&'w str, Error> {
48✔
97
    self.nibble_whitespace(slice);
48✔
98
    let starting_ptr = self.ptr;
48✔
99

100
    loop {
101
      if let Some(c) = self.peek_next(slice) {
280✔
102
        if c.is_whitespace() || DELIMITERS.contains(&c) {
276✔
103
          return Ok(&slice[starting_ptr..self.ptr]);
44✔
104
        }
232✔
105
        let _ = self.nibble_next(slice);
232✔
106
      } else {
107
        return Err(Error {
4✔
108
          code: Code::UnexpectedEOF,
4✔
109
          column: Some(self.column),
4✔
110
          line: Some(self.line),
4✔
111
          ptr: Some(self.ptr),
4✔
112
        });
4✔
113
      }
114
    }
115
  }
48✔
116

117
  // Nibbles away until the next new line
118
  #[inline(always)]
119
  fn nibble_newline(&mut self, slice: &str) {
12✔
120
    let len = slice[self.ptr..].split('\n').next().expect("Expected at least an empty slice");
12✔
121
    self.ptr += len.len();
12✔
122
    self.nibble_whitespace(slice);
12✔
123
  }
12✔
124

125
  // Nibbles away until the start of the next form
126
  #[inline(always)]
127
  fn nibble_whitespace(&mut self, slice: &str) {
1,988✔
128
    while let Some(n) = self.peek_next(slice) {
3,028✔
129
      if n == ',' || n.is_whitespace() {
3,005✔
130
        let _ = self.nibble_next(slice);
1,040✔
131
        continue;
1,040✔
132
      }
1,965✔
133
      break;
1,965✔
134
    }
135
  }
1,988✔
136

137
  // Consumes next
138
  #[inline(always)]
139
  fn nibble_next<'w>(&'w mut self, slice: &'w str) -> Option<char> {
2,924✔
140
    let char = slice[self.ptr..].chars().next();
2,924✔
141
    if let Some(c) = char {
2,924✔
142
      self.ptr += c.len_utf8();
2,914✔
143
      if c == '\n' {
2,914✔
144
        self.line += 1;
62✔
145
        self.column = 1;
62✔
146
      } else {
2,852✔
147
        self.column += 1;
2,852✔
148
      }
2,852✔
149
    }
10✔
150
    char
2,924✔
151
  }
2,924✔
152

153
  // Peek into the next char
154
  #[inline(always)]
155
  fn peek_next(&self, slice: &str) -> Option<char> {
5,610✔
156
    slice[self.ptr..].chars().next()
5,610✔
157
  }
5,610✔
158
}
159

160
pub fn parse(edn: &str) -> Result<(Edn<'_>, &str), Error> {
278✔
161
  let mut walker = Walker::default();
278✔
162
  let internal_parse = parse_internal(&mut walker, edn)?;
278✔
163
  internal_parse
222✔
164
    .map_or_else(|| Ok((Edn::Nil, &edn[walker.ptr..])), |ip| Ok((ip, &edn[walker.ptr..])))
222✔
165
}
278✔
166

167
#[inline]
168
fn parse_internal<'e>(walker: &mut Walker, slice: &'e str) -> Result<Option<Edn<'e>>, Error> {
1,094✔
169
  walker.nibble_whitespace(slice);
1,094✔
170
  while let Some(next) = walker.peek_next(slice) {
1,106✔
171
    let column_start = walker.column;
1,092✔
172
    let ptr_start = walker.ptr;
1,092✔
173
    let line_start = walker.line;
1,092✔
174
    if let Some(ret) = match next {
1,092✔
175
      '\\' => match parse_char(walker.slurp_char(slice)) {
78✔
176
        Ok(edn) => Some(Ok(edn)),
76✔
177
        Err(code) => {
2✔
178
          return Err(Error {
2✔
179
            code,
2✔
180
            line: Some(walker.line),
2✔
181
            column: Some(column_start),
2✔
182
            ptr: Some(walker.ptr),
2✔
183
          });
2✔
184
        }
185
      },
186
      '\"' => Some(Ok(Edn::Str(walker.slurp_str(slice)?))),
122✔
187
      // comment. consume until a new line.
188
      ';' => {
189
        walker.nibble_newline(slice);
12✔
190
        None
12✔
191
      }
192
      '[' => return Ok(Some(parse_vector(walker, slice, ']')?)),
44✔
193
      '(' => return Ok(Some(parse_vector(walker, slice, ')')?)),
74✔
194
      '{' => return Ok(Some(parse_map(walker, slice)?)),
103✔
195
      '#' => parse_tag_set_discard(walker, slice)?.map(Ok),
81✔
196
      // non-string literal case
197
      _ => match edn_literal(walker.slurp_literal(slice)) {
578✔
198
        Ok(edn) => match edn {
565✔
199
          Some(e) => Some(Ok(e)),
558✔
200
          None => {
201
            return Ok(None);
7✔
202
          }
203
        },
204
        Err(code) => {
13✔
205
          return Err(Error {
13✔
206
            code,
13✔
207
            line: Some(line_start),
13✔
208
            column: Some(column_start),
13✔
209
            ptr: Some(ptr_start),
13✔
210
          });
13✔
211
        }
212
      },
213
    } {
214
      return Ok(Some(ret?));
809✔
215
    }
12✔
216
  }
217
  Ok(None)
14✔
218
}
1,094✔
219

220
#[inline]
221
fn parse_tag_set_discard<'e>(
81✔
222
  walker: &mut Walker,
81✔
223
  slice: &'e str,
81✔
224
) -> Result<Option<Edn<'e>>, Error> {
81✔
225
  let _ = walker.nibble_next(slice); // Consume the leading '#' char
81✔
226

227
  match walker.peek_next(slice) {
81✔
228
    Some('{') => parse_set(walker, slice).map(Some),
17✔
229
    Some('_') => parse_discard(walker, slice),
16✔
230
    _ => parse_tag(walker, slice).map(Some),
48✔
231
  }
232
}
81✔
233

234
#[inline]
235
fn parse_discard<'e>(walker: &mut Walker, slice: &'e str) -> Result<Option<Edn<'e>>, Error> {
16✔
236
  let _ = walker.nibble_next(slice); // Consume the leading '_' char
16✔
237
  Ok(match parse_internal(walker, slice)? {
16✔
238
    None => {
239
      return Err(Error {
2✔
240
        code: Code::UnexpectedEOF,
2✔
241
        line: Some(walker.line),
2✔
242
        column: Some(walker.column),
2✔
243
        ptr: Some(walker.ptr),
2✔
244
      });
2✔
245
    }
246
    _ => match walker.peek_next(slice) {
14✔
247
      Some(_) => parse_internal(walker, slice)?,
10✔
248
      None => return Ok(Some(Edn::Nil)),
4✔
249
    },
250
  })
251
}
16✔
252

253
#[inline]
254
fn parse_set<'e>(walker: &mut Walker, slice: &'e str) -> Result<Edn<'e>, Error> {
17✔
255
  let _ = walker.nibble_next(slice); // Consume the leading '{' char
17✔
256
  let mut set: BTreeSet<Edn<'_>> = BTreeSet::new();
17✔
257

258
  loop {
259
    walker.nibble_whitespace(slice);
73✔
260
    match walker.peek_next(slice) {
73✔
261
      Some('}') => {
262
        let _ = walker.nibble_next(slice);
11✔
263
        return Ok(Edn::Set(set));
11✔
264
      }
265
      Some(n) => {
60✔
266
        if n == ']' || n == ')' {
60✔
267
          return Err(Error {
2✔
268
            code: Code::UnmatchedDelimiter(n),
2✔
269
            line: Some(walker.line),
2✔
270
            column: Some(walker.column),
2✔
271
            ptr: Some(walker.ptr),
2✔
272
          });
2✔
273
        }
58✔
274

275
        if let Some(n) = parse_internal(walker, slice)? {
58✔
276
          if !set.insert(n) {
58✔
277
            return Err(Error {
2✔
278
              code: Code::SetDuplicateKey,
2✔
279
              line: Some(walker.line),
2✔
280
              column: Some(walker.column),
2✔
281
              ptr: Some(walker.ptr),
2✔
282
            });
2✔
283
          }
56✔
284
        }
×
285
      }
286
      _ => {
287
        return Err(Error {
2✔
288
          code: Code::UnexpectedEOF,
2✔
289
          line: Some(walker.line),
2✔
290
          column: Some(walker.column),
2✔
291
          ptr: Some(walker.ptr),
2✔
292
        });
2✔
293
      }
294
    }
295
  }
296
}
17✔
297

298
#[inline]
299
fn parse_tag<'e>(walker: &mut Walker, slice: &'e str) -> Result<Edn<'e>, Error> {
48✔
300
  let tag = walker.slurp_tag(slice)?;
48✔
301
  walker.nibble_whitespace(slice);
44✔
302
  let val = parse_internal(walker, slice)?;
44✔
303
  if let Some(v) = val {
42✔
304
    return Ok(Edn::Tagged(tag, Box::new(v)));
40✔
305
  }
2✔
306

307
  Err(Error {
2✔
308
    code: Code::UnexpectedEOF,
2✔
309
    line: Some(walker.line),
2✔
310
    column: Some(walker.column),
2✔
311
    ptr: Some(walker.ptr),
2✔
312
  })
2✔
313
}
48✔
314

315
#[inline]
316
fn parse_map<'e>(walker: &mut Walker, slice: &'e str) -> Result<Edn<'e>, Error> {
103✔
317
  let _ = walker.nibble_next(slice); // Consume the leading '{' char
103✔
318
  let mut map: BTreeMap<Edn<'_>, Edn<'_>> = BTreeMap::new();
103✔
319
  loop {
320
    walker.nibble_whitespace(slice);
273✔
321
    match walker.peek_next(slice) {
273✔
322
      Some('}') => {
323
        let _ = walker.nibble_next(slice);
87✔
324
        return Ok(Edn::Map(map));
87✔
325
      }
326
      Some(n) => {
184✔
327
        if n == ']' || n == ')' {
184✔
328
          return Err(Error {
4✔
329
            code: Code::UnmatchedDelimiter(n),
4✔
330
            line: Some(walker.line),
4✔
331
            column: Some(walker.column),
4✔
332
            ptr: Some(walker.ptr),
4✔
333
          });
4✔
334
        }
180✔
335

336
        let key = parse_internal(walker, slice)?;
180✔
337
        let val = parse_internal(walker, slice)?;
176✔
338

339
        // When this is not true, errors are caught on the next loop
340
        if let (Some(k), Some(v)) = (key, val) {
174✔
341
          // Existing keys are considered an error
342
          if map.insert(k, v).is_some() {
168✔
343
            return Err(Error {
4✔
344
              code: Code::HashMapDuplicateKey,
4✔
345
              line: Some(walker.line),
4✔
346
              column: Some(walker.column),
4✔
347
              ptr: Some(walker.ptr),
4✔
348
            });
4✔
349
          }
164✔
350
        }
6✔
351
      }
352
      _ => {
353
        return Err(Error {
2✔
354
          code: Code::UnexpectedEOF,
2✔
355
          line: Some(walker.line),
2✔
356
          column: Some(walker.column),
2✔
357
          ptr: Some(walker.ptr),
2✔
358
        });
2✔
359
      }
360
    }
361
  }
362
}
103✔
363

364
#[inline]
365
fn parse_vector<'e>(walker: &mut Walker, slice: &'e str, delim: char) -> Result<Edn<'e>, Error> {
118✔
366
  let _ = walker.nibble_next(slice); // Consume the leading '[' or '(' char
118✔
367
  let mut vec = Vec::new();
118✔
368

369
  loop {
370
    walker.nibble_whitespace(slice);
444✔
371
    match walker.peek_next(slice) {
444✔
372
      Some(p) => {
439✔
373
        if p == delim {
439✔
374
          let _ = walker.nibble_next(slice);
107✔
375
          if delim == ']' {
107✔
376
            return Ok(Edn::Vector(vec));
40✔
377
          }
67✔
378

379
          return Ok(Edn::List(vec));
67✔
380
        }
332✔
381

382
        if let Some(next) = parse_internal(walker, slice)? {
332✔
383
          vec.push(next);
323✔
384
        } else if let Some(p) = walker.peek_next(slice) {
323✔
385
          if p != delim {
3✔
386
            let _ = walker.nibble_next(slice);
1✔
387
          }
2✔
388
        }
×
389
      }
390
      _ => {
391
        return Err(Error {
5✔
392
          code: Code::UnexpectedEOF,
5✔
393
          line: Some(walker.line),
5✔
394
          column: Some(walker.column),
5✔
395
          ptr: Some(walker.ptr),
5✔
396
        });
5✔
397
      }
398
    }
399
  }
400
}
118✔
401

402
#[inline]
403
fn edn_literal(literal: &str) -> Result<Option<Edn<'_>>, Code> {
578✔
404
  fn numeric(s: &str) -> bool {
374✔
405
    let (first, second) = {
374✔
406
      let mut s = s.chars();
374✔
407
      (s.next(), s.next())
374✔
408
    };
374✔
409

410
    let first = first.expect("Empty str is previously caught as nil");
374✔
411
    if first.is_numeric() {
374✔
412
      return true;
230✔
413
    }
144✔
414

415
    if first == '-' || first == '+' {
144✔
416
      if let Some(s) = second {
46✔
417
        if s.is_numeric() {
44✔
418
          return true;
32✔
419
        }
12✔
420
      }
2✔
421
    }
98✔
422

423
    false
112✔
424
  }
374✔
425

426
  Ok(match literal {
374✔
427
    "nil" => Some(Edn::Nil),
578✔
428
    "true" => Some(Edn::Bool(true)),
570✔
429
    "false" => Some(Edn::Bool(false)),
564✔
430
    "" => None,
561✔
431
    k if k.starts_with(':') => {
554✔
432
      if k.len() <= 1 {
180✔
433
        return Err(Code::InvalidKeyword);
2✔
434
      }
178✔
435
      Some(Edn::Key(&k[1..]))
178✔
436
    }
437
    n if numeric(n) => return Ok(Some(parse_number(n)?)),
374✔
438
    _ => Some(Edn::Symbol(literal)),
112✔
439
  })
440
}
578✔
441

442
#[inline]
443
fn parse_char(lit: &str) -> Result<Edn<'_>, Code> {
78✔
444
  let lit = &lit[1..]; // ignore the leading '\\'
78✔
445
  match lit {
62✔
446
    "newline" => Ok(Edn::Char('\n')),
78✔
447
    "return" => Ok(Edn::Char('\r')),
74✔
448
    "tab" => Ok(Edn::Char('\t')),
70✔
449
    "space" => Ok(Edn::Char(' ')),
66✔
450
    c if c.len() == 1 => Ok(Edn::Char(c.chars().next().expect("c must be len of 1"))),
62✔
451
    _ => Err(Code::InvalidChar),
2✔
452
  }
453
}
78✔
454

455
#[inline]
456
fn parse_number(lit: &str) -> Result<Edn<'_>, Code> {
262✔
457
  let mut chars = lit.chars().peekable();
262✔
458
  let (number, radix, polarity) = {
258✔
459
    let mut num_ptr_start = 0;
262✔
460
    let polarity = chars.peek().map_or(1i8, |c| {
262✔
461
      if *c == '-' {
262✔
462
        num_ptr_start += 1;
28✔
463
        -1i8
28✔
464
      } else if *c == '+' {
234✔
465
        // The EDN spec allows for a redundant '+' symbol, we just ignore it.
466
        num_ptr_start += 1;
4✔
467
        1i8
4✔
468
      } else {
469
        1i8
230✔
470
      }
471
    });
262✔
472

473
    let mut number = &lit[num_ptr_start..];
262✔
474

475
    if number.to_lowercase().starts_with("0x") {
262✔
476
      number = &number[2..];
20✔
477
      (number, 16, polarity)
20✔
478
    } else if let Some(index) = number.to_lowercase().find('r') {
242✔
479
      let radix = (number[0..index]).parse::<u8>();
14✔
480

481
      match radix {
14✔
482
        Ok(r) => {
12✔
483
          // from_str_radix panics if radix is not in the range from 2 to 36
484
          if !(2..=36).contains(&r) {
12✔
485
            return Err(Code::InvalidRadix(Some(r)));
2✔
486
          }
10✔
487

488
          number = &number[(index + 1)..];
10✔
489
          (number, r, polarity)
10✔
490
        }
491
        Err(_) => {
492
          return Err(Code::InvalidRadix(None));
2✔
493
        }
494
      }
495
    } else {
496
      (number, 10, polarity)
228✔
497
    }
498
  };
499

500
  if let Ok(n) = i64::from_str_radix(number, radix.into()) {
258✔
501
    return Ok(Edn::Int(n * i64::from(polarity)));
216✔
502
  }
42✔
503
  if let Some((n, d)) = num_den_from_slice(number, polarity) {
42✔
504
    return Ok(Edn::Rational((n, d)));
15✔
505
  }
27✔
506

507
  #[cfg(feature = "arbitrary-nums")]
508
  if let Some(n) = big_int_from_slice(number, radix, polarity) {
23✔
509
    return Ok(Edn::BigInt(n));
5✔
510
  }
18✔
511
  #[cfg(feature = "floats")]
512
  if let Ok(n) = number.parse::<f64>() {
18✔
513
    return Ok(Edn::Double((n * f64::from(polarity)).into()));
8✔
514
  }
10✔
515
  #[cfg(feature = "arbitrary-nums")]
516
  if let Some(n) = big_dec_from_slice(number, radix, polarity) {
10✔
517
    return Ok(Edn::BigDec(n));
7✔
518
  }
3✔
519

520
  Err(Code::InvalidNumber)
7✔
521
}
262✔
522

523
#[inline]
524
#[cfg(feature = "arbitrary-nums")]
525
fn big_int_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<num_bigint::BigInt> {
23✔
526
  // strip ending N, if it exists
527
  let slice = slice.strip_suffix('N').map_or(slice, |slice| slice);
23✔
528
  let num = num_bigint::BigInt::parse_bytes(slice.as_bytes(), radix.into())?;
23✔
529
  Some(num * polarity)
5✔
530
}
23✔
531

532
#[inline]
533
#[cfg(feature = "arbitrary-nums")]
534
fn big_dec_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<bigdecimal::BigDecimal> {
10✔
535
  // strip ending M, if it exists
536
  let slice = slice.strip_suffix('M').map_or(slice, |slice| slice);
10✔
537
  let num = bigdecimal::BigDecimal::parse_bytes(slice.as_bytes(), radix.into())?;
10✔
538
  Some(num * polarity)
7✔
539
}
10✔
540

541
#[inline]
542
fn num_den_from_slice(slice: &str, polarity: i8) -> Option<(i64, i64)> {
42✔
543
  let index = slice.find('/');
42✔
544

545
  if let Some(i) = index {
42✔
546
    let (num, den) = slice.split_at(i);
17✔
547
    let num = num.parse::<i64>();
17✔
548
    let den = den[1..].parse::<i64>();
17✔
549

550
    if let (Ok(n), Ok(d)) = (num, den) {
17✔
551
      return Some((n * i64::from(polarity), d));
15✔
552
    }
2✔
553
  }
25✔
554
  None
27✔
555
}
42✔
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