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

Grinkers / clojure-reader / 13342358208

15 Feb 2025 06:00AM UTC coverage: 98.276% (-0.1%) from 98.372%
13342358208

push

github

Grinkers
Run examples as tests (+coverage).

1026 of 1044 relevant lines covered (98.28%)

95.73 hits per line

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

99.25
/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 {
237✔
22
    Self { ptr: 0, column: 1, line: 1 }
237✔
23
  }
237✔
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 {
468✔
30
    let token = slice[self.ptr..]
468✔
31
      .split(|c: char| c.is_whitespace() || DELIMITERS.contains(&c))
3,486✔
32
      .next()
468✔
33
      .expect("Expected at least an empty slice");
468✔
34

468✔
35
    self.ptr += token.len();
468✔
36
    self.column += token.len();
468✔
37
    token
468✔
38
  }
468✔
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

78✔
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

230✔
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> {
58✔
61
    let _ = self.nibble_next(slice); // Consume the leading '"' char
58✔
62
    let starting_ptr = self.ptr;
58✔
63
    let mut escape = false;
58✔
64
    loop {
65
      if let Some(c) = self.nibble_next(slice) {
459✔
66
        if escape {
455✔
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 == '\"' {
451✔
80
          return Ok(&slice[starting_ptr..self.ptr - 1]);
52✔
81
        } else {
399✔
82
          escape = c == '\\';
399✔
83
        }
399✔
84
      } else {
85
        return Err(Error {
4✔
86
          code: Code::UnexpectedEOF,
4✔
87
          column: Some(self.column),
4✔
88
          line: Some(self.line),
4✔
89
          ptr: Some(self.ptr),
4✔
90
        });
4✔
91
      }
92
    }
93
  }
58✔
94

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

100
    loop {
101
      if let Some(c) = self.nibble_next(slice) {
170✔
102
        if c == ' ' {
166✔
103
          return Ok(&slice[starting_ptr..self.ptr - 1]);
23✔
104
        }
143✔
105
      } else {
106
        return Err(Error {
4✔
107
          code: Code::UnexpectedEOF,
4✔
108
          column: Some(self.column),
4✔
109
          line: Some(self.line),
4✔
110
          ptr: Some(self.ptr),
4✔
111
        });
4✔
112
      }
113
    }
114
  }
27✔
115

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

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

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

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

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

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

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

58✔
226
  match walker.peek_next(slice) {
58✔
227
    Some('{') => parse_set(walker, slice).map(Some),
15✔
228
    Some('_') => parse_discard(walker, slice),
16✔
229
    _ => parse_tag(walker, slice).map(Some),
27✔
230
  }
231
}
58✔
232

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

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

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

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

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

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

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

335
        let key = parse_internal(walker, slice)?;
109✔
336
        let val = parse_internal(walker, slice)?;
107✔
337

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

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

368
  loop {
369
    walker.nibble_whitespace(slice);
408✔
370
    match walker.peek_next(slice) {
408✔
371
      Some(p) => {
403✔
372
        if p == delim {
403✔
373
          let _ = walker.nibble_next(slice);
103✔
374
          if delim == ']' {
103✔
375
            return Ok(Edn::Vector(vec));
38✔
376
          }
65✔
377

65✔
378
          return Ok(Edn::List(vec));
65✔
379
        }
300✔
380

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

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

340✔
409
    let first = first.expect("Empty str is previously caught as nil");
340✔
410
    if first.is_numeric() {
340✔
411
      return true;
202✔
412
    }
138✔
413

138✔
414
    if first == '-' || first == '+' {
138✔
415
      if let Some(s) = second {
44✔
416
        if s.is_numeric() {
42✔
417
          return true;
32✔
418
        }
10✔
419
      }
2✔
420
    }
94✔
421

422
    false
106✔
423
  }
340✔
424

425
  Ok(match literal {
340✔
426
    "nil" => Some(Edn::Nil),
468✔
427
    "true" => Some(Edn::Bool(true)),
460✔
428
    "false" => Some(Edn::Bool(false)),
454✔
429
    "" => None,
451✔
430
    k if k.starts_with(':') => {
444✔
431
      if k.len() <= 1 {
104✔
432
        return Err(Code::InvalidKeyword);
2✔
433
      }
102✔
434
      Some(Edn::Key(&k[1..]))
102✔
435
    }
436
    n if numeric(n) => return Ok(Some(parse_number(n)?)),
340✔
437
    _ => Some(Edn::Symbol(literal)),
106✔
438
  })
439
}
468✔
440

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

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

234✔
472
    let mut number = &lit[num_ptr_start..];
234✔
473

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

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

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

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

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

3✔
519
  Err(Code::InvalidNumber)
3✔
520
}
234✔
521

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

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

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

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

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

© 2025 Coveralls, Inc