1#![deny(warnings)]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "ranges")]
5pub mod range;
6mod utils;
7
8use std::fmt::{Binary, Display, LowerHex, Octal};
9
10use num_traits::Num;
11use utils::{is_floaty, process_fractional, process_integer};
12
13pub const IMPLICIT_OCTAL_ENABLED: bool = {
15 #[cfg(feature = "implicit-octal")]
16 {
17 true
18 }
19 #[cfg(not(feature = "implicit-octal"))]
20 {
21 false
22 }
23};
24
25#[inline]
62pub fn parse<T: Num>(input: &str) -> Result<T, T::FromStrRadixErr> {
63 let input = input.trim();
64
65 let (is_negative, input) = if let Some(input) = input.strip_prefix('-') {
66 (true, input)
67 } else {
68 (false, input)
69 };
70
71 if input.starts_with("_") {
73 return T::from_str_radix("_", 2);
83 }
84
85 let num: T =
86 if input.starts_with("0x") || input.starts_with("0X") {
88 parse_with_base(&input[2..], 16)?
89 } else
90
91 if input.starts_with("0b") || input.starts_with("0B") {
93 parse_with_base(&input[2..], 2)?
94 } else
95
96 if input.starts_with("0o") || input.starts_with("0O") {
98 parse_with_base(&input[2..], 8)?
99 } else if IMPLICIT_OCTAL_ENABLED && input.starts_with("0") {
100 if input == "0" {
101 T::zero()
102 } else {
103 parse_with_base(&input[1..], 8)?
104 }
105 } else {
106 parse_with_base(input, 10)?
108 };
109
110 Ok(if is_negative {
111 num * (T::zero() - T::one())
112 } else {
113 num
114 })
115}
116
117#[inline]
118fn parse_with_base<T: Num>(input: &str, base: u32) -> Result<T, T::FromStrRadixErr> {
119 let input = input.chars().filter(|&c| c != '_').collect::<String>();
120 T::from_str_radix(&input, base)
121}
122
123pub fn format_pretty_dec<N: Num + Display + 'static>(num: N) -> String {
131 let is_floaty = is_floaty(&num);
132 let short = format!("{num}");
133
134 let (integer, fraction) = if is_floaty {
135 match short.split_once('.') {
136 Some((i, f)) => (i, Some(f)),
137 None => (short.as_ref(), Some("0")),
138 }
139 } else {
140 (short.as_ref(), None)
141 };
142
143 let processed_integer = process_integer(integer, 3);
144
145 if let Some(fraction) = fraction {
146 let processed_fractional = process_fractional(fraction);
147 format!("{processed_integer}.{processed_fractional}")
148 } else {
149 processed_integer
150 }
151}
152
153pub fn format_pretty_octal<N: Num + Display + Octal + PartialOrd + 'static>(num: N) -> String {
162 let is_negative = num < N::zero();
163
164 let num = if is_negative {
165 num * (N::zero() - N::one())
167 } else {
168 num
169 };
170
171 let short = format!("{num:o}");
172
173 let sign = if is_negative { "-" } else { "" };
174
175 let processed = process_integer(&short, 3);
176
177 format!("{sign}0o{processed}")
178}
179
180pub fn format_pretty_hex<N: Num + Display + LowerHex + PartialOrd + 'static>(num: N) -> String {
189 let is_negative = num < N::zero();
190
191 let num = if is_negative {
192 num * (N::zero() - N::one())
194 } else {
195 num
196 };
197
198 let short = format!("{num:x}");
199
200 let sign = if is_negative { "-" } else { "" };
201
202 let processed = process_integer(&short, 2);
203
204 format!("{sign}0x{processed}")
205}
206
207pub fn format_pretty_bin<N: Num + Display + Binary + PartialOrd + 'static>(num: N) -> String {
216 let is_negative = num < N::zero();
217
218 let num = if is_negative {
219 num * (N::zero() - N::one())
221 } else {
222 num
223 };
224
225 let short = format!("{num:b}");
226
227 let sign = if is_negative { "-" } else { "" };
228
229 let processed = process_integer(&short, 4);
230
231 format!("{sign}0b{processed}")
232}
233
234#[cfg(test)]
255mod test_parsing {
256 use super::*;
257
258 #[test]
259 fn turbofish_usize_dec() {
260 let s = "42";
261
262 let u = parse::<usize>(s).unwrap();
263
264 assert_eq!(42, u);
265 }
266
267 #[test]
268 fn deduct_usize_dec() {
269 let s = "42";
270
271 let u = parse(s).unwrap();
272
273 assert_eq!(42usize, u);
274 }
275
276 #[test]
277 fn deduct_isize_dec_neg() {
278 let s = "-42";
279
280 let u = parse(s).unwrap();
281
282 assert_eq!(-42_isize, u);
283 }
284
285 macro_rules! int_parse {
286 ($type:ident, $s:literal, $e:literal) => {
287 #[test]
288 fn $type() {
289 let l: Result<$type, _> = crate::parse($s);
290 assert_eq!(Ok($e), l, "lower case");
291
292 let u: Result<$type, _> = crate::parse(&$s.to_uppercase());
293 assert_eq!(Ok($e), u, "upper case");
294 }
295 };
296 }
297
298 macro_rules! int_parse_err {
299 ($type:ident, $s:literal) => {
300 int_parse_err!($type, $s, err);
301 };
302 ($type:ident, $s:literal, $opt:ident) => {
303 mod $opt {
304 #[test]
305 fn $type() {
306 let u: Result<$type, _> = crate::parse($s);
307 assert!(u.is_err(), "expected Err(_), got = {:?}", u);
308 }
309 }
310 };
311 }
312
313 mod decimal {
314 int_parse!(usize, "42", 42);
315 int_parse!(isize, "42", 42);
316 int_parse!(u8, "42", 42);
317 int_parse!(i8, "42", 42);
318 int_parse!(u16, "42", 42);
319 int_parse!(i16, "42", 42);
320 int_parse!(u32, "42", 42);
321 int_parse!(i32, "42", 42);
322 int_parse!(u64, "42", 42);
323 int_parse!(i64, "42", 42);
324 int_parse!(u128, "42", 42);
325 int_parse!(i128, "42", 42);
326 }
327
328 mod decimal_negative {
329 int_parse!(isize, "-42", -42);
330 int_parse!(i8, "-42", -42);
331 int_parse!(i128, "-42_000", -42_000);
332 }
333
334 mod hexadecimal {
335 int_parse!(usize, "0x42", 66);
336 int_parse!(isize, "0x42", 66);
337 int_parse!(u8, "0x42", 66);
338 int_parse!(i8, "0x42", 66);
339 int_parse!(u16, "0x42", 66);
340 int_parse!(i16, "0x42", 66);
341 int_parse!(u32, "0x42", 66);
342 int_parse!(i32, "0x42", 66);
343 int_parse!(u64, "0x42", 66);
344 int_parse!(i64, "0x42", 66);
345 int_parse!(u128, "0x42", 66);
346 int_parse!(i128, "0x42", 66);
347 }
348
349 mod hex_negative {
350 int_parse!(isize, "-0x42", -66);
351 int_parse!(i8, "-0x42", -66);
352 int_parse!(i16, "-0x42", -66);
353 int_parse!(i32, "-0x42", -66);
354 int_parse!(i64, "-0x42", -66);
355 int_parse!(i128, "-0x42", -66);
356 }
357
358 mod octal_explicit {
359 int_parse!(usize, "0o42", 34);
360 int_parse!(isize, "0o42", 34);
361 int_parse!(u8, "0o42", 34);
362 int_parse!(i8, "0o42", 34);
363 int_parse!(u16, "0o42", 34);
364 int_parse!(i16, "0o42", 34);
365 int_parse!(u32, "0o42", 34);
366 int_parse!(i32, "0o42", 34);
367 int_parse!(u64, "0o42", 34);
368 int_parse!(i64, "0o42", 34);
369 int_parse!(u128, "0o42", 34);
370 int_parse!(i128, "0o42", 34);
371 }
372
373 mod octal_explicit_negative {
374 int_parse!(isize, "-0o42", -34);
375 int_parse!(i8, "-0o42", -34);
376 int_parse!(i16, "-0o42", -34);
377 int_parse!(i32, "-0o42", -34);
378 int_parse!(i64, "-0o42", -34);
379 int_parse!(i128, "-0o42", -34);
380 }
381
382 #[cfg(feature = "implicit-octal")]
383 mod octal_implicit {
384 use super::*;
385 int_parse!(usize, "042", 34);
386 int_parse!(isize, "042", 34);
387 int_parse!(u8, "042", 34);
388 int_parse!(i8, "042", 34);
389 int_parse!(u16, "042", 34);
390 int_parse!(i16, "042", 34);
391 int_parse!(u32, "042", 34);
392 int_parse!(i32, "042", 34);
393 int_parse!(u64, "042", 34);
394 int_parse!(i64, "042", 34);
395 int_parse!(u128, "042", 34);
396 int_parse!(i128, "042", 34);
397
398 #[test]
399 fn issue_nr_0() {
400 let s = "0";
401
402 assert_eq!(0, parse::<usize>(s).unwrap());
403 assert_eq!(0, parse::<isize>(s).unwrap());
404 assert_eq!(0, parse::<i8>(s).unwrap());
405 assert_eq!(0, parse::<u8>(s).unwrap());
406 assert_eq!(0, parse::<i16>(s).unwrap());
407 assert_eq!(0, parse::<u16>(s).unwrap());
408 assert_eq!(0, parse::<i32>(s).unwrap());
409 assert_eq!(0, parse::<u32>(s).unwrap());
410 assert_eq!(0, parse::<i64>(s).unwrap());
411 assert_eq!(0, parse::<u64>(s).unwrap());
412 assert_eq!(0, parse::<i128>(s).unwrap());
413 assert_eq!(0, parse::<u128>(s).unwrap());
414 }
415 }
416 #[cfg(feature = "implicit-octal")]
417 mod octal_implicit_negative {
418 int_parse!(isize, "-042", -34);
419 int_parse!(i8, "-042", -34);
420 int_parse!(i16, "-042", -34);
421 int_parse!(i32, "-042", -34);
422 int_parse!(i64, "-042", -34);
423 int_parse!(i128, "-042", -34);
424 }
425 #[cfg(not(feature = "implicit-octal"))]
426 mod octal_implicit_disabled {
427 use super::*;
428 #[test]
429 fn no_implicit_is_int() {
431 let s = "042";
432
433 let u = parse::<usize>(s);
434 assert_eq!(Ok(42), u, "{:?}", u);
435 }
436 #[test]
437 fn no_implicit_is_int_neg() {
438 let s = "-042";
439
440 let u = parse::<isize>(s);
441 assert_eq!(Ok(-42), u, "{:?}", u);
442 }
443 }
444
445 mod binary {
446 int_parse!(usize, "0b0110", 6);
447 int_parse!(isize, "0b0110", 6);
448 int_parse!(u8, "0b0110", 6);
449 int_parse!(i8, "0b0110", 6);
450 int_parse!(u16, "0b0110", 6);
451 int_parse!(i16, "0b0110", 6);
452 int_parse!(u32, "0b0110", 6);
453 int_parse!(i32, "0b0110", 6);
454 int_parse!(u64, "0b0110", 6);
455 int_parse!(i64, "0b0110", 6);
456 int_parse!(u128, "0b0110", 6);
457 int_parse!(i128, "0b0110", 6);
458 }
459
460 mod binary_negative {
461 int_parse_err!(i8, "0b1000_0000");
462 int_parse!(i8, "0b-0111_1111", -127);
463 }
464
465 mod underscore {
466 int_parse!(usize, "0b0110_0110", 102);
467 int_parse!(isize, "0x0110_0110", 17_826_064);
468 int_parse!(u64, "0o0110_0110", 294_984);
469 int_parse!(u128, "1_100_110", 1_100_110);
470
471 #[cfg(feature = "implicit-octal")]
472 mod implicit_octal {
473 int_parse!(i128, "0110_0110", 294_984);
474 }
475 #[cfg(not(feature = "implicit-octal"))]
476 mod implicit_octal {
477 int_parse!(i128, "0110_0110", 1_100_110);
478 }
479 }
480
481 mod underscore_in_prefix {
482 #[test]
483 fn invalid_underscore_in_prefix() {
484 let r = crate::parse::<isize>("_4");
485 println!("{:?}", r);
486 assert!(r.is_err());
487 }
488 int_parse_err!(isize, "0_x_4", hex);
489 int_parse_err!(isize, "_4", decimal);
490 int_parse_err!(isize, "0_o_4", octal);
491 int_parse_err!(isize, "0_b_1", binary);
492 }
493}
494
495#[cfg(test)]
496mod test_parsing_floats {
497 use std::f64::consts::PI;
498
499 use super::*;
500
501 #[test]
502 fn dec() {
503 let f = parse("12.34");
504 assert_eq!(12.34, f.unwrap());
505 }
506
507 #[test]
508 fn dec_separators() {
509 assert_eq!(12.34, parse("1_2.3_4").unwrap());
510 assert_eq!(12.34_f32, parse("1_2.3_4").unwrap());
511 assert_eq!(12.34_f64, parse("1_2.3_4").unwrap());
512
513 assert_eq!(PI, parse("3.141_592_653_589_793").unwrap());
514 }
515}
516
517#[cfg(test)]
518mod pretty_print_test {
519 use super::*;
520
521 #[test]
522 fn intuitive_syntax() {
523 assert_eq!("42_230_123", format_pretty_dec(42_230_123));
524
525 assert_eq!("42.0", format_pretty_dec(42.0_f32));
526 }
527
528 #[test]
529 fn positive_ints_based() {
530 assert_eq!("42", format_pretty_dec(42));
531 assert_eq!("42_000", format_pretty_dec(42_000));
532
533 assert_eq!("-42_000", format_pretty_dec(-42_000));
534 }
535
536 #[test]
537 fn positive_ints_oct() {
538 let ft = 0o42;
539 assert_eq!("0o42", format_pretty_octal(ft));
540 assert_eq!("0o42_000", format_pretty_octal(0o42_000));
541
542 assert_eq!("0o42_000", format_pretty_octal(0o42_000_u64));
543 assert_eq!("0o42_000", format_pretty_octal(0o42_000_i64));
544 }
545
546 #[test]
547 fn positive_ints_oct_neg() {
548 let ft = -0o42;
549 assert_eq!("-0o42", format_pretty_octal(ft));
550 assert_eq!("-0o42_000", format_pretty_octal(-0o42_000));
551 assert_eq!("-0o4_200", format_pretty_octal(-0o4_200));
552
553 assert_eq!("-0o42_000", format_pretty_octal(-0o42_000_i64));
554 }
555
556 #[test]
557 fn positive_ints_hex() {
558 assert_eq!("0x42", format_pretty_hex(0x42));
559
560 assert_eq!("0xf_ff", format_pretty_hex(0xf_ff));
561 assert_eq!("0x42_00", format_pretty_hex(0x4_200));
562
563 assert_eq!("0xff_ff", format_pretty_hex(0xff_FF));
564 assert_eq!("0x4_20_00", format_pretty_hex(0x42_000));
565
566 assert_eq!("0x4_20_00", format_pretty_hex(0x42_000_u64));
567 assert_eq!("0x4_20_00", format_pretty_hex(0x42_000_i64));
568 }
569
570 #[test]
571 fn negative_ints_hex() {
572 assert_eq!("-0x42", format_pretty_hex(-0x42_i8));
573 assert_eq!("-0x42", format_pretty_hex(-0x42_i16));
574
575 assert_eq!("-0x42_00", format_pretty_hex(-0x42_00_i32));
576 assert_eq!("-0x4_20_00", format_pretty_hex(-0x4_20_00_i64));
577 }
578
579 #[test]
580 fn dec_ints() {
581 assert_eq!("0", format_pretty_dec(0));
582 assert_eq!("42", format_pretty_dec(42));
583 assert_eq!("42_000", format_pretty_dec(42_000));
584
585 assert_eq!("-4_200", format_pretty_dec(-4200));
586 assert_eq!("-42_000", format_pretty_dec(-42_000));
587 }
588
589 #[test]
590 fn dec_floats() {
591 assert_eq!("0.0", format_pretty_dec(0.0_f32));
592 assert_eq!("0.0", format_pretty_dec(0.0_f64));
593
594 assert_eq!("42.0", format_pretty_dec(42.0_f32));
595 assert_eq!("42_000.0", format_pretty_dec(42_000.0_f64));
596
597 assert_eq!("-42_000.0", format_pretty_dec(-42_000.0));
598 assert_eq!("-42_000.001_002", format_pretty_dec(-42_000.001_002));
599 }
600
601 #[test]
602 fn bin_ints() {
603 assert_eq!("0b0", format_pretty_bin(0));
604 assert_eq!("0b10_1010", format_pretty_bin(42));
605 assert_eq!("0b1010_0100_0001_0000", format_pretty_bin(42_000));
606
607 assert_eq!("-0b1_0000_0110_1000", format_pretty_bin(-4200));
608 assert_eq!("-0b1010_0100_0001_0000", format_pretty_bin(-42_000));
609 }
610}