History

inline btoi code to reduce compile times

Benedikt Mandelkow committed 2 years ago

Commit f26f2981 parent b8def7713 files · +470 −35Browse files

Unified · Split

13 files
  1. Cargo.lock +5 −5
  2. gix-actor/Cargo.toml +9 −4
  3. gix-actor/src/signature/decode.rs +1 −1
  4. gix-index/Cargo.toml +12 −4
  5. gix-index/src/extension/tree/decode.rs +2 −2
  6. gix-object/Cargo.toml +20 −7
  7. gix-object/src/lib.rs +2 −2
  8. gix-protocol/Cargo.toml +18 −6
  9. gix-protocol/src/remote_progress.rs +1 −1
  10. gix-quote/Cargo.toml +3 −2
  11. gix-quote/src/ansi_c.rs +2 −1
  12. gix-utils/src/btoi.rs +392 −0
  13. gix-utils/src/lib.rs +3 −0
Cargo.lock+5−5
@@ -1429,12 +1429,12 @@ name = "gix-actor" · 1428 unmodified lines
14291429 version = "0.30.0"
14301430 dependencies = [
14311431 "bstr",
1432− "btoi",
14331432 "document-features",
14341433 "gix-date 0.8.3",
14351434 "gix-features 0.38.0",
14361435 "gix-hash 0.14.1",
14371436 "gix-testtools",
1437+ "gix-utils 0.1.9",
14381438 "itoa",
14391439 "pretty_assertions",
14401440 "serde",
@@ -2002,7 +2002,6 @@ version = "0.30.0" · 561 unmodified lines
20022002 dependencies = [
20032003 "bitflags 2.4.1",
20042004 "bstr",
2005− "btoi",
20062005 "document-features",
20072006 "filetime",
20082007 "fnv",
@@ -2013,6 +2012,7 @@ dependencies = [ · 4 unmodified lines
20132012 "gix-lock 13.1.0",
20142013 "gix-object 0.41.0",
20152014 "gix-traverse 0.37.0",
2015+ "gix-utils 0.1.9",
20162016 "hashbrown 0.14.3",
20172017 "itoa",
20182018 "libc",
@@ -2159,7 +2159,6 @@ name = "gix-object" · 140 unmodified lines
21592159 version = "0.41.0"
21602160 dependencies = [
21612161 "bstr",
2162− "btoi",
21632162 "criterion",
21642163 "document-features",
21652164 "gix-actor 0.30.0",
@@ -2167,6 +2166,7 @@ dependencies = [ · 1 unmodified line
21672166 "gix-features 0.38.0",
21682167 "gix-hash 0.14.1",
21692168 "gix-testtools",
2169+ "gix-utils 0.1.9",
21702170 "gix-validate 0.8.3",
21712171 "itoa",
21722172 "pretty_assertions",
@@ -2347,7 +2347,6 @@ dependencies = [ · 174 unmodified lines
23472347 "async-std",
23482348 "async-trait",
23492349 "bstr",
2350− "btoi",
23512350 "document-features",
23522351 "futures-io",
23532352 "futures-lite 2.1.0",
@@ -2358,6 +2357,7 @@ dependencies = [ · 4 unmodified lines
23582357 "gix-packetline",
23592358 "gix-testtools",
23602359 "gix-transport",
2360+ "gix-utils 0.1.9",
23612361 "maybe-async",
23622362 "serde",
23632363 "thiserror",
@@ -2380,7 +2380,7 @@ name = "gix-quote" · 16 unmodified lines
23802380 version = "0.4.10"
23812381 dependencies = [
23822382 "bstr",
2383− "btoi",
2383+ "gix-utils 0.1.9",
23842384 "thiserror",
23852385 ]
23862386
gix-actor/Cargo.toml+9−4
@@ -19,19 +19,24 @@ serde = ["dep:serde", "bstr/serde", "gix-date/serde"] · 18 unmodified lines
1919 [dependencies]
2020 gix-features = { version = "^0.38.0", path = "../gix-features", optional = true }
2121 gix-date = { version = "^0.8.3", path = "../gix-date" }
22+gix-utils = { version = "^0.1.9", path = "../gix-utils" }
2223
2324 thiserror = "1.0.38"
24−btoi = "0.4.2"
25−bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"]}
25+bstr = { version = "1.3.0", default-features = false, features = [
26+ "std",
27+ "unicode",
28+] }
2629 winnow = { version = "0.6.0", features = ["simd"] }
2730 itoa = "1.0.1"
28−serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
31+serde = { version = "1.0.114", optional = true, default-features = false, features = [
32+ "derive",
33+] }
2934
3035 document-features = { version = "0.2.0", optional = true }
3136
3237 [dev-dependencies]
3338 pretty_assertions = "1.0.0"
34−gix-testtools = { path = "../tests/tools"}
39+gix-testtools = { path = "../tests/tools" }
3540 gix-hash = { path = "../gix-hash" }
3641
3742 [package.metadata.docs.rs]
gix-actor/src/signature/decode.rs+1−1
@@ -1,7 +1,7 @@
11 pub(crate) mod function {
22 use bstr::ByteSlice;
3− use btoi::btoi;
43 use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};
4+ use gix_utils::btoi::btoi;
55 use winnow::{
66 combinator::{alt, separated_pair, terminated},
77 error::{AddContext, ParserError, StrContext},
gix-index/Cargo.toml+12−4
@@ -20,13 +20,17 @@ test = true · 19 unmodified lines
2020 serde = ["dep:serde", "smallvec/serde", "gix-hash/serde"]
2121
2222 [dependencies]
23−gix-features = { version = "^0.38.0", path = "../gix-features", features = ["rustsha1", "progress"] }
23+gix-features = { version = "^0.38.0", path = "../gix-features", features = [
24+ "rustsha1",
25+ "progress",
26+] }
2427 gix-hash = { version = "^0.14.1", path = "../gix-hash" }
2528 gix-bitmap = { version = "^0.2.10", path = "../gix-bitmap" }
2629 gix-object = { version = "^0.41.0", path = "../gix-object" }
2730 gix-traverse = { version = "^0.37.0", path = "../gix-traverse" }
2831 gix-lock = { version = "^13.0.0", path = "../gix-lock" }
2932 gix-fs = { version = "^0.10.0", path = "../gix-fs" }
33+gix-utils = { version = "^0.1.9", path = "../gix-utils" }
3034
3135 hashbrown = "0.14.3"
3236 fnv = "1.0.7"
@@ -35,16 +39,20 @@ memmap2 = "0.9.0" · 2 unmodified lines
3539 filetime = "0.2.15"
3640 bstr = { version = "1.3.0", default-features = false }
3741
38−serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
42+serde = { version = "1.0.114", optional = true, default-features = false, features = [
43+ "derive",
44+] }
3945 smallvec = "1.7.0"
40−btoi = "0.4.3"
4146 itoa = "1.0.3"
4247 bitflags = "2"
4348
4449 document-features = { version = "0.2.0", optional = true }
4550
4651 [target.'cfg(not(windows))'.dependencies]
47−rustix = { version = "0.38.20", default-features = false, features = ["std", "fs"] }
52+rustix = { version = "0.38.20", default-features = false, features = [
53+ "std",
54+ "fs",
55+] }
4856 libc = { version = "0.2.149" }
4957
5058 [package.metadata.docs.rs]
gix-index/src/extension/tree/decode.rs+2−2
@@ -22,10 +22,10 @@ fn one_recursive(data: &[u8], hash_len: usize) -> Option<(Tree, &[u8])> { · 21 unmodified lines
2222 let (path, data) = split_at_byte_exclusive(data, 0)?;
2323
2424 let (entry_count, data) = split_at_byte_exclusive(data, b' ')?;
25− let num_entries: i32 = btoi::btoi(entry_count).ok()?;
25+ let num_entries: i32 = gix_utils::btoi::btoi(entry_count).ok()?;
2626
2727 let (subtree_count, data) = split_at_byte_exclusive(data, b'\n')?;
28− let subtree_count: usize = btoi::btou(subtree_count).ok()?;
28+ let subtree_count: usize = gix_utils::btoi::btou(subtree_count).ok()?;
2929
3030 let (id, mut data) = if num_entries >= 0 {
3131 let (hash, data) = split_at_pos(data, hash_len)?;
gix-object/Cargo.toml+20−7
@@ -20,7 +20,13 @@ path = "./benches/decode_objects.rs" · 19 unmodified lines
2020
2121 [features]
2222 ## Data structures implement `serde::Serialize` and `serde::Deserialize`.
23−serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-actor/serde"]
23+serde = [
24+ "dep:serde",
25+ "bstr/serde",
26+ "smallvec/serde",
27+ "gix-hash/serde",
28+ "gix-actor/serde",
29+]
2430 ## When parsing objects by default errors will only be available on the granularity of success or failure, and with the above flag enabled
2531 ## details information about the error location will be collected.
2632 ## Use it in applications which expect broken or invalid objects or for debugging purposes. Incorrectly formatted objects aren't at all
@@ -28,28 +34,35 @@ serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-act · 1 unmodified line
2834 verbose-object-parsing-errors = ["winnow/std"]
2935
3036 [dependencies]
31−gix-features = { version = "^0.38.0", path = "../gix-features", features = ["rustsha1", "progress"] }
37+gix-features = { version = "^0.38.0", path = "../gix-features", features = [
38+ "rustsha1",
39+ "progress",
40+] }
3241 gix-hash = { version = "^0.14.1", path = "../gix-hash" }
3342 gix-validate = { version = "^0.8.3", path = "../gix-validate" }
3443 gix-actor = { version = "^0.30.0", path = "../gix-actor" }
3544 gix-date = { version = "^0.8.3", path = "../gix-date" }
45+gix-utils = { version = "^0.1.9", path = "../gix-utils" }
3646
37−btoi = "0.4.2"
3847 itoa = "1.0.1"
3948 thiserror = "1.0.34"
40−bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }
49+bstr = { version = "1.3.0", default-features = false, features = [
50+ "std",
51+ "unicode",
52+] }
4153 winnow = { version = "0.6.0", features = ["simd"] }
4254 smallvec = { version = "1.4.0", features = ["write"] }
43−serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
55+serde = { version = "1.0.114", optional = true, default-features = false, features = [
56+ "derive",
57+] }
4458
4559 document-features = { version = "0.2.0", optional = true }
4660
4761 [dev-dependencies]
4862 criterion = "0.5.1"
4963 pretty_assertions = "1.0.0"
50−gix-testtools = { path = "../tests/tools"}
64+gix-testtools = { path = "../tests/tools" }
5165
5266 [package.metadata.docs.rs]
5367 all-features = true
5468 features = ["document-features"]
55−
gix-object/src/lib.rs+2−2
@@ -358,7 +358,7 @@ pub mod decode { · 357 unmodified lines
358358 pub enum LooseHeaderDecodeError {
359359 #[error("{message}: {number:?}")]
360360 ParseIntegerError {
361− source: btoi::ParseIntegerError,
361+ source: gix_utils::btoi::ParseIntegerError,
362362 message: &'static str,
363363 number: bstr::BString,
364364 },
@@ -383,7 +383,7 @@ pub mod decode { · 18 unmodified lines
383383 message: "Did not find 0 byte in header",
384384 })?;
385385 let size_bytes = &input[kind_end + 1..size_end];
386− let size = btoi::btoi(size_bytes).map_err(|source| ParseIntegerError {
386+ let size = gix_utils::btoi::btoi(size_bytes).map_err(|source| ParseIntegerError {
387387 source,
388388 message: "Object size in header could not be parsed",
389389 number: size_bytes.into(),
gix-protocol/Cargo.toml+18−6
@@ -23,7 +23,12 @@ doctest = false · 22 unmodified lines
2323 ## If set, blocking command implementations are available and will use the blocking version of the `gix-transport` crate.
2424 blocking-client = ["gix-transport/blocking-client", "maybe-async/is_sync"]
2525 ## As above, but provides async implementations instead.
26−async-client = ["gix-transport/async-client", "async-trait", "futures-io", "futures-lite"]
26+async-client = [
27+ "gix-transport/async-client",
28+ "async-trait",
29+ "futures-io",
30+ "futures-lite",
31+]
2732
2833 #! ### Other
2934 ## Data structures implement `serde::Serialize` and `serde::Deserialize`.
@@ -40,17 +45,24 @@ path = "tests/async-protocol.rs" · 10 unmodified lines
4045 required-features = ["async-client"]
4146
4247 [dependencies]
43−gix-features = { version = "^0.38.0", path = "../gix-features", features = ["progress"] }
48+gix-features = { version = "^0.38.0", path = "../gix-features", features = [
49+ "progress",
50+] }
4451 gix-transport = { version = "^0.41.0", path = "../gix-transport" }
4552 gix-hash = { version = "^0.14.1", path = "../gix-hash" }
4653 gix-date = { version = "^0.8.3", path = "../gix-date" }
4754 gix-credentials = { version = "^0.24.0", path = "../gix-credentials" }
55+gix-utils = { version = "^0.1.9", path = "../gix-utils" }
4856
4957 thiserror = "1.0.32"
50−serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
51−bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }
58+serde = { version = "1.0.114", optional = true, default-features = false, features = [
59+ "derive",
60+] }
61+bstr = { version = "1.3.0", default-features = false, features = [
62+ "std",
63+ "unicode",
64+] }
5265 winnow = { version = "0.6.0", features = ["simd"] }
53−btoi = "0.4.2"
5466
5567 # for async-client
5668 async-trait = { version = "0.1.51", optional = true }
@@ -62,7 +74,7 @@ document-features = { version = "0.2.0", optional = true } · 5 unmodified lines
6274
6375 [dev-dependencies]
6476 async-std = { version = "1.9.0", features = ["attributes"] }
65−gix-packetline = { path = "../gix-packetline" ,version = "^0.17.3" }
77+gix-packetline = { path = "../gix-packetline", version = "^0.17.3" }
6678 gix-testtools = { path = "../tests/tools" }
6779
6880 [package.metadata.docs.rs]
gix-protocol/src/remote_progress.rs+1−1
@@ -75,7 +75,7 @@ impl<'a> RemoteProgress<'a> { · 74 unmodified lines
7575
7676 fn parse_number(i: &mut &[u8]) -> PResult<usize, ()> {
7777 take_till(0.., |c: u8| !c.is_ascii_digit())
78− .try_map(btoi::btoi)
78+ .try_map(gix_utils::btoi::btoi)
7979 .parse_next(i)
8080 }
8181
gix-quote/Cargo.toml+3−2
@@ -13,6 +13,7 @@ include = ["src/**/*", "LICENSE-*"] · 12 unmodified lines
1313 doctest = false
1414
1515 [dependencies]
16−bstr = { version = "1.3.0", default-features = false, features = ["std"]}
16+gix-utils = { version = "^0.1.9", path = "../gix-utils" }
17+
18+bstr = { version = "1.3.0", default-features = false, features = ["std"] }
1719 thiserror = "1.0.38"
18−btoi = "0.4.2"
gix-quote/src/ansi_c.rs+2−1
@@ -89,7 +89,8 @@ pub fn undo(input: &BStr) -> Result<(Cow<'_, BStr>, usize), undo::Error> { · 88 unmodified lines
8989 })?
9090 .read_exact(&mut buf[1..])
9191 .expect("impossible to fail as numbers match");
92− let byte = btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?;
92+ let byte =
93+ gix_utils::btoi::btou_radix(&buf, 8).map_err(|e| undo::Error::new(e, original))?;
9394 out.push(byte);
9495 input = &input[2..];
9596 consumed += 2;
gix-utils/src/btoi.rs+392−0
@@ -0,0 +1,392 @@
1+// ported from https://github.com/niklasf/rust-btoi version 0.4.3
2+// see https://github.com/Byron/gitoxide/issues/729#issuecomment-1941515655
3+
4+/// An error that can occur when parsing an integer.
5+///
6+/// * No digits
7+/// * Invalid digit
8+/// * Overflow
9+/// * Underflow
10+#[derive(Debug, Clone, PartialEq, Eq)]
11+pub struct ParseIntegerError {
12+ kind: ErrorKind,
13+}
14+
15+#[derive(Debug, Clone, PartialEq, Eq)]
16+enum ErrorKind {
17+ Empty,
18+ InvalidDigit,
19+ Overflow,
20+ Underflow,
21+}
22+
23+impl ParseIntegerError {
24+ fn desc(&self) -> &str {
25+ match self.kind {
26+ ErrorKind::Empty => "cannot parse integer without digits",
27+ ErrorKind::InvalidDigit => "invalid digit found in slice",
28+ ErrorKind::Overflow => "number too large to fit in target type",
29+ ErrorKind::Underflow => "number too small to fit in target type",
30+ }
31+ }
32+}
33+
34+impl std::fmt::Display for ParseIntegerError {
35+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36+ self.desc().fmt(f)
37+ }
38+}
39+
40+impl std::error::Error for ParseIntegerError {
41+ fn description(&self) -> &str {
42+ self.desc()
43+ }
44+}
45+/// minimal subset of traits used by btoi_radix and btou_radix
46+pub trait MinNumTraits: Sized + Copy {
47+ ///
48+ fn from_u32(n: u32) -> Option<Self>;
49+ ///
50+ fn zero() -> Self;
51+ ///
52+ fn checked_mul(self, v: Self) -> Option<Self>;
53+ ///
54+ fn checked_add(self, v: Self) -> Option<Self>;
55+ ///
56+ fn checked_sub(self, v: Self) -> Option<Self>;
57+}
58+
59+impl MinNumTraits for i64 {
60+ fn from_u32(n: u32) -> Option<i64> {
61+ Some(n.into())
62+ }
63+
64+ fn zero() -> Self {
65+ 0
66+ }
67+
68+ fn checked_mul(self, v: i64) -> Option<i64> {
69+ i64::checked_mul(self, v)
70+ }
71+
72+ fn checked_add(self, v: i64) -> Option<i64> {
73+ i64::checked_add(self, v)
74+ }
75+
76+ fn checked_sub(self, v: i64) -> Option<i64> {
77+ i64::checked_sub(self, v)
78+ }
79+}
80+
81+impl MinNumTraits for u64 {
82+ fn from_u32(n: u32) -> Option<u64> {
83+ Some(n.into())
84+ }
85+
86+ fn zero() -> Self {
87+ 0
88+ }
89+
90+ fn checked_mul(self, v: u64) -> Option<u64> {
91+ u64::checked_mul(self, v)
92+ }
93+
94+ fn checked_add(self, v: u64) -> Option<u64> {
95+ u64::checked_add(self, v)
96+ }
97+
98+ fn checked_sub(self, v: u64) -> Option<u64> {
99+ u64::checked_sub(self, v)
100+ }
101+}
102+
103+impl MinNumTraits for usize {
104+ fn from_u32(n: u32) -> Option<usize> {
105+ n.try_into().ok()
106+ }
107+
108+ fn zero() -> Self {
109+ 0
110+ }
111+
112+ fn checked_mul(self, v: usize) -> Option<usize> {
113+ usize::checked_mul(self, v)
114+ }
115+
116+ fn checked_add(self, v: usize) -> Option<usize> {
117+ usize::checked_add(self, v)
118+ }
119+
120+ fn checked_sub(self, v: usize) -> Option<usize> {
121+ usize::checked_sub(self, v)
122+ }
123+}
124+
125+impl MinNumTraits for i32 {
126+ fn from_u32(n: u32) -> Option<Self> {
127+ n.try_into().ok()
128+ }
129+
130+ fn zero() -> Self {
131+ 0
132+ }
133+
134+ fn checked_mul(self, v: i32) -> Option<i32> {
135+ i32::checked_mul(self, v)
136+ }
137+
138+ fn checked_add(self, v: i32) -> Option<i32> {
139+ i32::checked_add(self, v)
140+ }
141+
142+ fn checked_sub(self, v: i32) -> Option<i32> {
143+ i32::checked_sub(self, v)
144+ }
145+}
146+
147+impl MinNumTraits for u8 {
148+ fn from_u32(n: u32) -> Option<Self> {
149+ n.try_into().ok()
150+ }
151+
152+ fn zero() -> Self {
153+ 0
154+ }
155+
156+ fn checked_mul(self, v: u8) -> Option<u8> {
157+ u8::checked_mul(self, v)
158+ }
159+
160+ fn checked_add(self, v: u8) -> Option<u8> {
161+ u8::checked_add(self, v)
162+ }
163+
164+ fn checked_sub(self, v: u8) -> Option<u8> {
165+ u8::checked_sub(self, v)
166+ }
167+}
168+
169+/// Converts a byte slice to an integer. Signs are not allowed.
170+///
171+/// # Errors
172+///
173+/// Returns [`ParseIntegerError`] for any of the following conditions:
174+///
175+/// * `bytes` is empty
176+/// * not all characters of `bytes` are `0-9`
177+/// * the number overflows `I`
178+///
179+/// # Panics
180+///
181+/// Panics in the pathological case that there is no representation of `10`
182+/// in `I`.
183+///
184+/// # Examples
185+///
186+/// ```
187+/// # use btoi::btou;
188+/// assert_eq!(Ok(12345), btou(b"12345"));
189+/// assert!(btou::<u8>(b"+1").is_err()); // only btoi allows signs
190+/// assert!(btou::<u8>(b"256").is_err()); // overflow
191+/// ```
192+///
193+/// [`ParseIntegerError`]: struct.ParseIntegerError.html
194+#[track_caller]
195+pub fn btou<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
196+ btou_radix(bytes, 10)
197+}
198+
199+/// Converts a byte slice in a given base to an integer. Signs are not allowed.
200+///
201+/// # Errors
202+///
203+/// Returns [`ParseIntegerError`] for any of the following conditions:
204+///
205+/// * `bytes` is empty
206+/// * not all characters of `bytes` are `0-9`, `a-z` or `A-Z`
207+/// * not all characters refer to digits in the given `radix`
208+/// * the number overflows `I`
209+///
210+/// # Panics
211+///
212+/// Panics if `radix` is not in the range `2..=36` (or in the pathological
213+/// case that there is no representation of `radix` in `I`).
214+///
215+/// # Examples
216+///
217+/// ```
218+/// # use btoi::btou_radix;
219+/// assert_eq!(Ok(255), btou_radix(b"ff", 16));
220+/// assert_eq!(Ok(42), btou_radix(b"101010", 2));
221+/// ```
222+///
223+/// [`ParseIntegerError`]: struct.ParseIntegerError.html
224+pub fn btou_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
225+ assert!(
226+ (2..=36).contains(&radix),
227+ "radix must lie in the range 2..=36, found {}",
228+ radix
229+ );
230+
231+ let base = I::from_u32(radix).expect("radix can be represented as integer");
232+
233+ if bytes.is_empty() {
234+ return Err(ParseIntegerError { kind: ErrorKind::Empty });
235+ }
236+
237+ let mut result = I::zero();
238+
239+ for &digit in bytes {
240+ let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
241+ Some(x) => x,
242+ None => {
243+ return Err(ParseIntegerError {
244+ kind: ErrorKind::InvalidDigit,
245+ })
246+ }
247+ };
248+ result = match result.checked_mul(base) {
249+ Some(result) => result,
250+ None => {
251+ return Err(ParseIntegerError {
252+ kind: ErrorKind::Overflow,
253+ })
254+ }
255+ };
256+ result = match result.checked_add(x) {
257+ Some(result) => result,
258+ None => {
259+ return Err(ParseIntegerError {
260+ kind: ErrorKind::Overflow,
261+ })
262+ }
263+ };
264+ }
265+
266+ Ok(result)
267+}
268+
269+/// Converts a byte slice to an integer.
270+///
271+/// Like [`btou`], but numbers may optionally start with a sign (`-` or `+`).
272+///
273+/// # Errors
274+///
275+/// Returns [`ParseIntegerError`] for any of the following conditions:
276+///
277+/// * `bytes` has no digits
278+/// * not all characters of `bytes` are `0-9`, excluding an optional leading
279+/// sign
280+/// * the number overflows or underflows `I`
281+///
282+/// # Panics
283+///
284+/// Panics in the pathological case that there is no representation of `10`
285+/// in `I`.
286+///
287+/// # Examples
288+///
289+/// ```
290+/// # use btoi::btoi;
291+/// assert_eq!(Ok(123), btoi(b"123"));
292+/// assert_eq!(Ok(123), btoi(b"+123"));
293+/// assert_eq!(Ok(-123), btoi(b"-123"));
294+///
295+/// assert!(btoi::<i16>(b"123456789").is_err()); // overflow
296+/// assert!(btoi::<u32>(b"-1").is_err()); // underflow
297+///
298+/// assert!(btoi::<i32>(b" 42").is_err()); // leading space
299+/// ```
300+///
301+/// [`btou`]: fn.btou.html
302+/// [`ParseIntegerError`]: struct.ParseIntegerError.html
303+pub fn btoi<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
304+ btoi_radix(bytes, 10)
305+}
306+
307+/// Converts a byte slice in a given base to an integer.
308+///
309+/// Like [`btou_radix`], but numbers may optionally start with a sign
310+/// (`-` or `+`).
311+///
312+/// # Errors
313+///
314+/// Returns [`ParseIntegerError`] for any of the following conditions:
315+///
316+/// * `bytes` has no digits
317+/// * not all characters of `bytes` are `0-9`, `a-z`, `A-Z`, exluding an
318+/// optional leading sign
319+/// * not all characters refer to digits in the given `radix`, exluding an
320+/// optional leading sign
321+/// * the number overflows or underflows `I`
322+///
323+/// # Panics
324+///
325+/// Panics if `radix` is not in the range `2..=36` (or in the pathological
326+/// case that there is no representation of `radix` in `I`).
327+///
328+/// # Examples
329+///
330+/// ```
331+/// # use btoi::btoi_radix;
332+/// assert_eq!(Ok(10), btoi_radix(b"a", 16));
333+/// assert_eq!(Ok(10), btoi_radix(b"+a", 16));
334+/// assert_eq!(Ok(-42), btoi_radix(b"-101010", 2));
335+/// ```
336+///
337+/// [`btou_radix`]: fn.btou_radix.html
338+/// [`ParseIntegerError`]: struct.ParseIntegerError.html
339+fn btoi_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
340+ assert!(
341+ (2..=36).contains(&radix),
342+ "radix must lie in the range 2..=36, found {}",
343+ radix
344+ );
345+
346+ let base = I::from_u32(radix).expect("radix can be represented as integer");
347+
348+ if bytes.is_empty() {
349+ return Err(ParseIntegerError { kind: ErrorKind::Empty });
350+ }
351+
352+ let digits = match bytes[0] {
353+ b'+' => return btou_radix(&bytes[1..], radix),
354+ b'-' => &bytes[1..],
355+ _ => return btou_radix(bytes, radix),
356+ };
357+
358+ if digits.is_empty() {
359+ return Err(ParseIntegerError { kind: ErrorKind::Empty });
360+ }
361+
362+ let mut result = I::zero();
363+
364+ for &digit in digits {
365+ let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
366+ Some(x) => x,
367+ None => {
368+ return Err(ParseIntegerError {
369+ kind: ErrorKind::InvalidDigit,
370+ })
371+ }
372+ };
373+ result = match result.checked_mul(base) {
374+ Some(result) => result,
375+ None => {
376+ return Err(ParseIntegerError {
377+ kind: ErrorKind::Underflow,
378+ })
379+ }
380+ };
381+ result = match result.checked_sub(x) {
382+ Some(result) => result,
383+ None => {
384+ return Err(ParseIntegerError {
385+ kind: ErrorKind::Underflow,
386+ })
387+ }
388+ };
389+ }
390+
391+ Ok(result)
392+}
gix-utils/src/lib.rs+3−0
@@ -13,6 +13,9 @@ pub mod buffers; · 12 unmodified lines
1313 ///
1414 pub mod str;
1515
16+///
17+pub mod btoi;
18+
1619 /// A utility to do buffer-swapping with.
1720 ///
1821 /// Use `src` to read from and `dest` to write to, and after actually changing data, call [Buffers::swap()].