Skip to content

Commit 7422a14

Browse files
committed
Atomic Bomb
0 parents  commit 7422a14

5 files changed

Lines changed: 252 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

Cargo.lock

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "prefix_parse"
3+
version = "1.0.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
num-traits = "0.2.19"
8+
tap = "1.0.1"
9+
thiserror = "2.0.16"

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# 📦 prefix_parse
2+
A lightweight Rust crate for parsing numeric strings with radix-indicating prefixes like 0x, 0o, and 0b. It provides a trait-based interface for parsing numbers with standard or custom prefixes, supporting any type that implements [num_traits::Num](https://docs.rs/num-traits/latest/num_traits/trait.Num.html). Which includes all base rust numeric types.
3+
4+
The implementation relies on `[from_str_radix](https://docs.rs/num-traits/0.2.19/num_traits/trait.Num.html#tymethod.from_str_radix)` internally, so the same limitations and capabilities present their apply.
5+
6+
## ✨ Features
7+
- ✅ Parse hexadecimal (0x), octal (0o), binary (0b), and decimal numbers
8+
- ✅ Support for custom prefix formats and arbitrary radices
9+
- ✅ Works with any type implementing Num, including u32, i64, u8, etc.
10+
11+
### Detect common prefixes
12+
```rust
13+
use prefix_parse::PrefixParse;
14+
15+
assert_eq!(u32::parse("0x10"), Ok(16));
16+
assert_eq!(u32::parse("0o10"), Ok(8));
17+
assert_eq!(u32::parse("0b10"), Ok(2));
18+
assert_eq!(u32::parse("10"), Ok(10));
19+
```
20+
21+
### Built-in Prefixes
22+
```rust
23+
use prefix_parse::{PrefixParse, HEX, OCT, BIN, DEC};
24+
25+
assert_eq!(u32::parse_with(&HEX, "0xFF"), Ok(255));
26+
assert_eq!(u32::parse_with(&OCT, "0o77"), Ok(63));
27+
assert_eq!(u32::parse_with(&BIN, "0b1010"), Ok(10));
28+
assert_eq!(u32::parse_with(&DEC, "42"), Ok(42));
29+
```
30+
31+
### Custom Prefixes
32+
```rust
33+
use prefix_parse::{PrefixFmt, PrefixParse};
34+
let base36 = PrefixFmt {
35+
prefix: "0z",
36+
radix: 36,
37+
};
38+
39+
assert_eq!(u32::parse_with(&base36, "0z1jz"), Ok(2015));
40+
```
41+
42+
## 🔧 Extending
43+
Any new type that implements `Num` from `num_traits` will automatically implement this.
44+
45+
## 📝 License
46+
MIT or Apache-2.0 — your choice.
47+
48+
## 💬 Feedback
49+
Contributions welcome!

src/lib.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use num_traits::Num;
2+
use tap::Pipe;
3+
4+
/// Defines a prefix format.
5+
#[derive(Debug)]
6+
pub struct PrefixFmt<'a> {
7+
pub prefix: &'a str,
8+
pub radix: u32,
9+
}
10+
11+
/// '0x' prefix for hexadecimal numbers
12+
pub const HEX: PrefixFmt = PrefixFmt {
13+
prefix: "0x",
14+
radix: 16,
15+
};
16+
/// '0o' prefix for octal numbers
17+
pub const OCT: PrefixFmt = PrefixFmt {
18+
prefix: "0o",
19+
radix: 8,
20+
};
21+
22+
/// '0b' prefix for binary numbers
23+
pub const BIN: PrefixFmt = PrefixFmt {
24+
prefix: "0b",
25+
radix: 2,
26+
};
27+
28+
/// '' prefix for decimal numbers
29+
pub const DEC: PrefixFmt = PrefixFmt {
30+
prefix: "",
31+
radix: 10,
32+
};
33+
34+
/// Trait for parsing prefixed numbers
35+
pub trait PrefixParse {
36+
/// Parse a number prefixed with `0x`, `0o`, and `0b`
37+
///
38+
/// # Example
39+
/// ```rust
40+
/// use prefix_parse::PrefixParse;
41+
///
42+
/// assert_eq!(u32::parse("0x10"), Ok(16));
43+
/// assert_eq!(u32::parse("0o10"), Ok(8));
44+
/// assert_eq!(u32::parse("0b10"), Ok(2));
45+
/// assert_eq!(u32::parse("10"), Ok(10));
46+
/// ```
47+
fn parse(src: &str) -> Result<Self, ParseError<Self>>
48+
where
49+
Self: Sized + Num,
50+
{
51+
// SAFETY: if src is a valid UTF-8 string, and we strip no multibyte characters from the start,
52+
// then the remaining string will be valid UTF-8
53+
match src.as_bytes() {
54+
[b'0', b'x', rest @ ..] => {
55+
Self::from_str_radix(unsafe { str::from_utf8_unchecked(rest) }, 16)
56+
.map_err(ParseError::RadixParseFailed)
57+
}
58+
[b'0', b'o', rest @ ..] => {
59+
Self::from_str_radix(unsafe { str::from_utf8_unchecked(rest) }, 8)
60+
.map_err(ParseError::RadixParseFailed)
61+
}
62+
[b'0', b'b', rest @ ..] => {
63+
Self::from_str_radix(unsafe { str::from_utf8_unchecked(rest) }, 2)
64+
.map_err(ParseError::RadixParseFailed)
65+
}
66+
_ => Self::from_str_radix(src, 10).map_err(ParseError::RadixParseFailed),
67+
}
68+
}
69+
70+
/// Parse a number with a custom prefix
71+
///
72+
/// # Example
73+
/// ```
74+
/// use prefix_parse::{PrefixParse, ParseError, PrefixFmt, HEX};
75+
///
76+
/// assert_eq!(u32::parse_with(&HEX, "0x10"), Ok(16));
77+
///
78+
/// let custom_fmt = PrefixFmt {
79+
/// prefix: "0z",
80+
/// radix: 36,
81+
/// };
82+
/// assert_eq!(u32::parse_with(&custom_fmt, "0z1jz"), Ok(2015));
83+
/// ```
84+
fn parse_with(fmt: &PrefixFmt, src: &str) -> Result<Self, ParseError<Self>>
85+
where
86+
Self: Sized + Num,
87+
{
88+
src.strip_prefix(fmt.prefix)
89+
.ok_or(ParseError::NoPrefixMatch)?
90+
.pipe(|rest| Self::from_str_radix(rest, fmt.radix))
91+
.map_err(ParseError::RadixParseFailed)
92+
}
93+
}
94+
95+
/// Implementation for all number types that implement the `Num` interface.
96+
impl<T: Num> PrefixParse for T {}
97+
98+
/// Error type for `PrefixParse`
99+
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
100+
pub enum ParseError<T: Num> {
101+
#[error("No Prefix Match")]
102+
NoPrefixMatch,
103+
#[error(transparent)]
104+
RadixParseFailed(T::FromStrRadixErr),
105+
}

0 commit comments

Comments
 (0)