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

Grinkers / clojure-reader / 20373659675

19 Dec 2025 02:55PM UTC coverage: 94.481%. Remained the same
20373659675

push

github

Grinkers
wip stack based instead of recursion based parsing

197 of 229 new or added lines in 1 file covered. (86.03%)

6 existing lines in 1 file now uncovered.

1010 of 1069 relevant lines covered (94.48%)

222.15 hits per line

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

91.24
/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<'e> {
15
  slice: &'e str,
16
  ptr: usize,
17
  column: usize,
18
  line: usize,
19
}
20

21
impl<'e> Walker<'e> {
22
  const fn new(slice: &'e str) -> Self {
290✔
23
    Self { slice, ptr: 0, column: 1, line: 1 }
290✔
24
  }
290✔
25
}
26

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

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

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

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

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

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

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

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

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

126
  // Nibbles away until the start of the next form
127
  #[inline(always)]
128
  fn nibble_whitespace(&mut self) {
4,382✔
129
    while let Some(n) = self.peek_next() {
5,694✔
130
      if n == ',' || n.is_whitespace() {
5,666✔
131
        let _ = self.nibble_next();
1,312✔
132
        continue;
1,312✔
133
      }
4,354✔
134
      break;
4,354✔
135
    }
136
  }
4,382✔
137

138
  // Consumes next
139
  #[inline(always)]
140
  fn nibble_next(&mut self) -> Option<char> {
5,311✔
141
    let char = self.slice[self.ptr..].chars().next();
5,311✔
142
    if let Some(c) = char {
5,311✔
143
      self.ptr += c.len_utf8();
5,301✔
144
      if c == '\n' {
5,301✔
145
        self.line += 1;
94✔
146
        self.column = 1;
94✔
147
      } else {
5,207✔
148
        self.column += 1;
5,207✔
149
      }
5,207✔
150
    }
10✔
151
    char
5,311✔
152
  }
5,311✔
153

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

161
#[derive(Debug)]
162
enum ParseContext<'e> {
163
  Top,
164
  Vector(Vec<Edn<'e>>),
165
  List(Vec<Edn<'e>>),
166
  Map(BTreeMap<Edn<'e>, Edn<'e>>, Option<Edn<'e>>),
167
  Set(BTreeSet<Edn<'e>>),
168
  Tag(&'e str),
169
  Discard,
170
}
171

172
#[inline]
173
fn parse_element<'e>(walker: &mut Walker<'e>) -> Result<Edn<'e>, Error> {
835✔
174
  let column_start = walker.column;
835✔
175
  let ptr_start = walker.ptr;
835✔
176
  let line_start = walker.line;
835✔
177
  walker.nibble_whitespace();
835✔
178
  if let Some(next) = walker.peek_next() {
835✔
179
    match next {
835✔
180
      '\\' => match parse_char(walker.slurp_char()) {
78✔
181
        Ok(edn) => Ok(edn),
76✔
182
        Err(code) => Err(Error {
2✔
183
          code,
2✔
184
          line: Some(line_start),
2✔
185
          column: Some(column_start),
2✔
186
          ptr: Some(ptr_start),
2✔
187
        }),
2✔
188
      },
189
      '\"' => Ok(Edn::Str(walker.slurp_str()?)),
134✔
190
      _ => match edn_literal(walker.slurp_literal()) {
623✔
191
        Ok(Some(edn)) => Ok(edn),
606✔
NEW
192
        Ok(None) => Err(Error {
×
NEW
193
          code: Code::UnexpectedEOF,
×
NEW
194
          line: Some(line_start),
×
NEW
195
          column: Some(column_start),
×
NEW
196
          ptr: Some(ptr_start),
×
NEW
197
        }),
×
198
        Err(code) => Err(Error {
17✔
199
          code,
17✔
200
          line: Some(line_start),
17✔
201
          column: Some(column_start),
17✔
202
          ptr: Some(ptr_start),
17✔
203
        }),
17✔
204
      },
205
    }
206
  } else {
NEW
207
    Err(Error {
×
NEW
208
      code: Code::UnexpectedEOF,
×
NEW
209
      line: Some(line_start),
×
NEW
210
      column: Some(column_start),
×
NEW
211
      ptr: Some(ptr_start),
×
NEW
212
    })
×
213
  }
214
}
835✔
215

216
#[inline]
217
fn add_to_context<'e>(
801✔
218
  ctx: &mut ParseContext<'e>,
801✔
219
  edn: Edn<'e>,
801✔
220
  walker: &Walker<'e>,
801✔
221
) -> Result<(), Error> {
801✔
222
  match ctx {
801✔
223
    ParseContext::Vector(vec) | ParseContext::List(vec) => vec.push(edn),
343✔
224
    ParseContext::Map(map, pending) => {
400✔
225
      if let Some(key) = pending.take() {
400✔
226
        if map.insert(key, edn).is_some() {
196✔
227
          return Err(Error {
4✔
228
            code: Code::HashMapDuplicateKey,
4✔
229
            line: Some(walker.line),
4✔
230
            column: Some(walker.column),
4✔
231
            ptr: Some(walker.ptr),
4✔
232
          });
4✔
233
        }
192✔
234
      } else {
204✔
235
        *pending = Some(edn);
204✔
236
      }
204✔
237
    }
238
    ParseContext::Set(set) => {
58✔
239
      if !set.insert(edn) {
58✔
240
        return Err(Error {
2✔
241
          code: Code::SetDuplicateKey,
2✔
242
          line: Some(walker.line),
2✔
243
          column: Some(walker.column),
2✔
244
          ptr: Some(walker.ptr),
2✔
245
        });
2✔
246
      }
56✔
247
    }
NEW
248
    _ => unreachable!(), // Tag and Discard handled in loop
×
249
  }
250
  Ok(())
795✔
251
}
801✔
252

253
pub fn parse(edn: &str) -> Result<(Edn<'_>, &str), Error> {
290✔
254
  let mut walker = Walker::new(edn);
290✔
255
  let internal_parse = parse_internal(&mut walker)?;
290✔
256
  internal_parse
228✔
257
    .map_or_else(|| Ok((Edn::Nil, &edn[walker.ptr..])), |ip| Ok((ip, &edn[walker.ptr..])))
228✔
258
}
290✔
259

260
#[inline]
261
fn handle_open_delimiter<'e>(
2,324✔
262
  walker: &mut Walker<'e>,
2,324✔
263
  stack: &mut Vec<ParseContext<'e>>,
2,324✔
264
) -> Result<(), Error> {
2,324✔
265
  match walker.peek_next() {
2,324✔
266
    Some('[') => {
1,552✔
267
      let _ = walker.nibble_next();
1,552✔
268
      stack.push(ParseContext::Vector(Vec::new()));
1,552✔
269
    }
1,552✔
270
    Some('(') => {
552✔
271
      let _ = walker.nibble_next();
552✔
272
      stack.push(ParseContext::List(Vec::new()));
552✔
273
    }
552✔
274
    Some('{') => {
135✔
275
      let _ = walker.nibble_next();
135✔
276
      stack.push(ParseContext::Map(BTreeMap::new(), None));
135✔
277
    }
135✔
278
    Some('#') => {
279
      let _ = walker.nibble_next();
85✔
280
      match walker.peek_next() {
85✔
281
        Some('{') => {
17✔
282
          let _ = walker.nibble_next();
17✔
283
          stack.push(ParseContext::Set(BTreeSet::new()));
17✔
284
        }
17✔
285
        Some('_') => {
20✔
286
          let _ = walker.nibble_next();
20✔
287
          stack.push(ParseContext::Discard);
20✔
288
        }
20✔
289
        _ => {
290
          let tag = walker.slurp_tag()?;
48✔
291
          walker.nibble_whitespace();
44✔
292
          stack.push(ParseContext::Tag(tag));
44✔
293
        }
294
      }
295
    }
NEW
296
    _ => unreachable!(),
×
297
  }
298
  Ok(())
2,320✔
299
}
2,324✔
300

301
#[inline]
302
fn handle_close_delimiter<'e>(
238✔
303
  walker: &mut Walker<'e>,
238✔
304
  delimiter: char,
238✔
305
  stack: &mut Vec<ParseContext<'e>>,
238✔
306
) -> Result<Option<Edn<'e>>, Error> {
238✔
307
  if stack.len() <= 1 {
238✔
NEW
308
    return Err(Error {
×
NEW
309
      code: Code::UnmatchedDelimiter(delimiter),
×
NEW
310
      line: Some(walker.line),
×
NEW
311
      column: Some(walker.column),
×
NEW
312
      ptr: Some(walker.ptr),
×
NEW
313
    });
×
314
  }
238✔
315
  let expected = match stack.last().expect("Len > 2 is never empty") {
238✔
316
    ParseContext::Vector(_) => ']',
46✔
317
    ParseContext::List(_) => ')',
76✔
318
    ParseContext::Map(_, _) | ParseContext::Set(_) => '}',
116✔
319
    _ => {
NEW
320
      return Err(Error {
×
NEW
321
        code: Code::UnmatchedDelimiter(delimiter),
×
NEW
322
        line: Some(walker.line),
×
NEW
323
        column: Some(walker.column),
×
NEW
324
        ptr: Some(walker.ptr),
×
NEW
325
      });
×
326
    }
327
  };
328
  if delimiter != expected {
238✔
329
    return Err(Error {
7✔
330
      code: Code::UnmatchedDelimiter(delimiter),
7✔
331
      line: Some(walker.line),
7✔
332
      column: Some(walker.column),
7✔
333
      ptr: Some(walker.ptr),
7✔
334
    });
7✔
335
  }
231✔
336
  let closed = stack.pop().expect("Len > 2 is never empty");
231✔
337
  let mut edn = match closed {
231✔
338
    ParseContext::Vector(vec) => Edn::Vector(vec),
46✔
339
    ParseContext::List(vec) => Edn::List(vec),
75✔
340
    ParseContext::Map(map, pending) => {
99✔
341
      if pending.is_some() {
99✔
UNCOV
342
        return Err(Error {
×
UNCOV
343
          code: Code::UnexpectedEOF,
×
UNCOV
344
          line: Some(walker.line),
×
UNCOV
345
          column: Some(walker.column),
×
UNCOV
346
          ptr: Some(walker.ptr),
×
UNCOV
347
        });
×
348
      }
99✔
349
      Edn::Map(map)
99✔
350
    }
351
    ParseContext::Set(set) => Edn::Set(set),
11✔
NEW
352
    _ => unreachable!(),
×
353
  };
354
  let _ = walker.nibble_next();
231✔
355
  if stack.len() == 1 {
231✔
356
    return Ok(Some(edn));
106✔
357
  }
125✔
358
  while stack.len() > 1 && matches!(stack.last(), Some(&ParseContext::Tag(_))) {
149✔
359
    if let Some(ParseContext::Tag(t)) = stack.last() {
24✔
360
      let t = *t;
24✔
361
      stack.pop();
24✔
362
      edn = Edn::Tagged(t, Box::new(edn));
24✔
363
    }
24✔
364
  }
365
  if stack.len() == 1 {
125✔
366
    return Ok(Some(edn));
8✔
367
  } else if matches!(stack.last(), Some(&ParseContext::Discard)) {
117✔
368
    stack.pop();
2✔
369
  } else {
2✔
370
    add_to_context(stack.last_mut().expect("Stack should not be empty"), edn, walker)?;
115✔
371
  }
372
  Ok(None)
117✔
373
}
238✔
374

375
#[inline]
376
fn handle_element<'e>(
835✔
377
  walker: &mut Walker<'e>,
835✔
378
  stack: &mut Vec<ParseContext<'e>>,
835✔
379
) -> Result<Option<Edn<'e>>, Error> {
835✔
380
  let edn = parse_element(walker)?;
835✔
381
  if stack.len() == 1 {
804✔
382
    return Ok(Some(edn));
87✔
383
  }
717✔
384
  match stack.last().expect("Stack should not be empty") {
717✔
385
    ParseContext::Tag(tag) => {
16✔
386
      let mut finalized = Edn::Tagged(tag, Box::new(edn));
16✔
387
      stack.pop();
16✔
388
      while stack.len() > 1 && matches!(stack.last(), Some(&ParseContext::Tag(_))) {
16✔
NEW
389
        if let Some(ParseContext::Tag(t)) = stack.last() {
×
NEW
390
          let t = *t;
×
NEW
391
          stack.pop();
×
NEW
392
          finalized = Edn::Tagged(t, Box::new(finalized));
×
NEW
393
        }
×
394
      }
395
      if stack.len() == 1 {
16✔
396
        return Ok(Some(finalized));
15✔
397
      }
1✔
398
      add_to_context(stack.last_mut().expect("Stack should not be empty"), finalized, walker)?;
1✔
399
    }
400
    ParseContext::Discard => {
16✔
401
      stack.pop();
16✔
402
    }
16✔
403
    _ => {
404
      add_to_context(stack.last_mut().expect("Stack should not be empty"), edn, walker)?;
685✔
405
    }
406
  }
407
  Ok(None)
696✔
408
}
835✔
409

410
fn parse_internal<'e>(walker: &mut Walker<'e>) -> Result<Option<Edn<'e>>, Error> {
290✔
411
  let mut stack: Vec<ParseContext<'e>> = Vec::new();
290✔
412
  stack.push(ParseContext::Top);
290✔
413
  let mut result: Option<Edn<'e>> = None;
290✔
414
  loop {
415
    walker.nibble_whitespace();
3,439✔
416
    match walker.peek_next() {
3,439✔
417
      Some(';') => {
16✔
418
        walker.nibble_newline();
16✔
419
      }
16✔
420
      Some('[' | '(' | '{' | '#') => {
421
        handle_open_delimiter(walker, &mut stack)?;
2,324✔
422
      }
423
      Some(d) if matches!(d, ']' | ')' | '}') => {
1,073✔
424
        if let Some(edn) = handle_close_delimiter(walker, d, &mut stack)? {
238✔
425
          result = Some(edn);
114✔
426
          break;
114✔
427
        }
117✔
428
      }
429
      Some(_) => {
430
        if let Some(edn) = handle_element(walker, &mut stack)? {
835✔
431
          result = Some(edn);
102✔
432
          break;
102✔
433
        }
696✔
434
      }
435
      None => {
436
        if stack.len() > 1 {
26✔
437
          return Err(Error {
14✔
438
            code: Code::UnexpectedEOF,
14✔
439
            line: Some(walker.line),
14✔
440
            column: Some(walker.column),
14✔
441
            ptr: Some(walker.ptr),
14✔
442
          });
14✔
443
        }
12✔
444
        break;
12✔
445
      }
446
    }
447
  }
448
  Ok(result)
228✔
449
}
290✔
450

451
#[inline]
452
fn edn_literal(literal: &str) -> Result<Option<Edn<'_>>, Code> {
623✔
453
  fn numeric(s: &str) -> bool {
400✔
454
    let (first, second) = {
400✔
455
      let mut s = s.chars();
400✔
456
      (s.next(), s.next())
400✔
457
    };
400✔
458

459
    let first = first.expect("Empty str is previously caught as nil");
400✔
460
    if first.is_numeric() {
400✔
461
      return true;
242✔
462
    }
158✔
463

464
    if (first == '-' || first == '+')
158✔
465
      && let Some(s) = second
50✔
466
      && s.is_numeric()
48✔
467
    {
468
      return true;
36✔
469
    }
122✔
470

471
    false
122✔
472
  }
400✔
473

474
  Ok(match literal {
400✔
475
    "nil" => Some(Edn::Nil),
623✔
476
    "true" => Some(Edn::Bool(true)),
615✔
477
    "false" => Some(Edn::Bool(false)),
609✔
478
    "" => None,
606✔
479
    k if k.starts_with(':') => {
606✔
480
      if k.len() <= 1 {
206✔
481
        return Err(Code::InvalidKeyword);
2✔
482
      }
204✔
483
      Some(Edn::Key(&k[1..]))
204✔
484
    }
485
    n if numeric(n) => return Ok(Some(parse_number(n)?)),
400✔
486
    _ => Some(Edn::Symbol(literal)),
122✔
487
  })
488
}
623✔
489

490
#[inline]
491
fn parse_char(lit: &str) -> Result<Edn<'_>, Code> {
78✔
492
  let lit = &lit[1..]; // ignore the leading '\\'
78✔
493
  match lit {
62✔
494
    "newline" => Ok(Edn::Char('\n')),
78✔
495
    "return" => Ok(Edn::Char('\r')),
74✔
496
    "tab" => Ok(Edn::Char('\t')),
70✔
497
    "space" => Ok(Edn::Char(' ')),
66✔
498
    c if c.len() == 1 => Ok(Edn::Char(c.chars().next().expect("c must be len of 1"))),
62✔
499
    _ => Err(Code::InvalidChar),
2✔
500
  }
501
}
78✔
502

503
#[inline]
504
fn parse_number(lit: &str) -> Result<Edn<'_>, Code> {
278✔
505
  let mut chars = lit.chars().peekable();
278✔
506
  let (number, radix, polarity) = {
274✔
507
    let mut num_ptr_start = 0;
278✔
508
    let polarity = chars.peek().map_or(1i8, |c| {
278✔
509
      if *c == '-' {
278✔
510
        num_ptr_start += 1;
32✔
511
        -1i8
32✔
512
      } else if *c == '+' {
246✔
513
        // The EDN spec allows for a redundant '+' symbol, we just ignore it.
514
        num_ptr_start += 1;
4✔
515
        1i8
4✔
516
      } else {
517
        1i8
242✔
518
      }
519
    });
278✔
520

521
    let mut number = &lit[num_ptr_start..];
278✔
522

523
    if number.to_lowercase().starts_with("0x") {
278✔
524
      number = &number[2..];
26✔
525
      (number, 16, polarity)
26✔
526
    } else if let Some(index) = number.to_lowercase().find('r') {
252✔
527
      let radix = (number[0..index]).parse::<u8>();
16✔
528

529
      match radix {
16✔
530
        Ok(r) => {
14✔
531
          // from_str_radix panics if radix is not in the range from 2 to 36
532
          if !(2..=36).contains(&r) {
14✔
533
            return Err(Code::InvalidRadix(Some(r)));
2✔
534
          }
12✔
535

536
          number = &number[(index + 1)..];
12✔
537
          (number, r, polarity)
12✔
538
        }
539
        Err(_) => {
540
          return Err(Code::InvalidRadix(None));
2✔
541
        }
542
      }
543
    } else {
544
      (number, 10, polarity)
236✔
545
    }
546
  };
547

548
  if let Ok(n) = i64::from_str_radix(number, radix.into()) {
274✔
549
    return Ok(Edn::Int(n * i64::from(polarity)));
224✔
550
  }
50✔
551
  if radix == 10
50✔
552
    && let Some((n, d)) = num_den_from_slice(number, polarity)
43✔
553
  {
554
    return Ok(Edn::Rational((n, d)));
19✔
555
  }
31✔
556

557
  #[cfg(feature = "arbitrary-nums")]
558
  if let Some(n) = big_int_from_slice(number, radix, polarity) {
25✔
559
    return Ok(Edn::BigInt(n));
5✔
560
  }
20✔
561
  #[cfg(feature = "floats")]
562
  if radix == 10
20✔
563
    && let Ok(n) = number.parse::<f64>()
17✔
564
  {
565
    return Ok(Edn::Double((n * f64::from(polarity)).into()));
8✔
566
  }
12✔
567
  #[cfg(feature = "arbitrary-nums")]
568
  if let Some(n) = big_dec_from_slice(number, radix, polarity) {
12✔
569
    return Ok(Edn::BigDec(n));
7✔
570
  }
5✔
571

572
  Err(Code::InvalidNumber)
11✔
573
}
278✔
574

575
#[inline]
576
#[cfg(feature = "arbitrary-nums")]
577
fn big_int_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<num_bigint::BigInt> {
25✔
578
  // strip ending N, if it exists
579
  let slice = slice.strip_suffix('N').map_or(slice, |slice| slice);
25✔
580
  let num = num_bigint::BigInt::parse_bytes(slice.as_bytes(), radix.into())?;
25✔
581
  Some(num * polarity)
5✔
582
}
25✔
583

584
#[inline]
585
#[cfg(feature = "arbitrary-nums")]
586
fn big_dec_from_slice(slice: &str, radix: u8, polarity: i8) -> Option<bigdecimal::BigDecimal> {
12✔
587
  // strip ending M, if it exists
588
  let slice = slice.strip_suffix('M').map_or(slice, |slice| slice);
12✔
589
  let num = bigdecimal::BigDecimal::parse_bytes(slice.as_bytes(), radix.into())?;
12✔
590
  Some(num * polarity)
7✔
591
}
12✔
592

593
#[inline]
594
fn num_den_from_slice(slice: &str, polarity: i8) -> Option<(i64, i64)> {
43✔
595
  let index = slice.find('/');
43✔
596

597
  if let Some(i) = index {
43✔
598
    let (num, den) = slice.split_at(i);
21✔
599
    let num = num.parse::<i64>();
21✔
600
    let den = den[1..].parse::<i64>();
21✔
601

602
    if let (Ok(n), Ok(d)) = (num, den) {
21✔
603
      return Some((n * i64::from(polarity), d));
19✔
604
    }
2✔
605
  }
22✔
606
  None
24✔
607
}
43✔
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