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
1429version = "0.30.0"1429version = "0.30.0"
1430dependencies = [1430dependencies = [
1431 "bstr",1431 "bstr",
1432 "btoi",
1433 "document-features",1432 "document-features",
1434 "gix-date 0.8.3",1433 "gix-date 0.8.3",
1435 "gix-features 0.38.0",1434 "gix-features 0.38.0",
1436 "gix-hash 0.14.1",1435 "gix-hash 0.14.1",
1437 "gix-testtools",1436 "gix-testtools",
1437 "gix-utils 0.1.9",
1438 "itoa",1438 "itoa",
1439 "pretty_assertions",1439 "pretty_assertions",
1440 "serde",1440 "serde",
@@ -2002,7 +2002,6 @@ version = "0.30.0" · 561 unmodified lines
2002dependencies = [2002dependencies = [
2003 "bitflags 2.4.1",2003 "bitflags 2.4.1",
2004 "bstr",2004 "bstr",
2005 "btoi",
2006 "document-features",2005 "document-features",
2007 "filetime",2006 "filetime",
2008 "fnv",2007 "fnv",
@@ -2013,6 +2012,7 @@ dependencies = [ · 4 unmodified lines
2013 "gix-lock 13.1.0",2012 "gix-lock 13.1.0",
2014 "gix-object 0.41.0",2013 "gix-object 0.41.0",
2015 "gix-traverse 0.37.0",2014 "gix-traverse 0.37.0",
2015 "gix-utils 0.1.9",
2016 "hashbrown 0.14.3",2016 "hashbrown 0.14.3",
2017 "itoa",2017 "itoa",
2018 "libc",2018 "libc",
@@ -2159,7 +2159,6 @@ name = "gix-object" · 140 unmodified lines
2159version = "0.41.0"2159version = "0.41.0"
2160dependencies = [2160dependencies = [
2161 "bstr",2161 "bstr",
2162 "btoi",
2163 "criterion",2162 "criterion",
2164 "document-features",2163 "document-features",
2165 "gix-actor 0.30.0",2164 "gix-actor 0.30.0",
@@ -2167,6 +2166,7 @@ dependencies = [ · 1 unmodified line
2167 "gix-features 0.38.0",2166 "gix-features 0.38.0",
2168 "gix-hash 0.14.1",2167 "gix-hash 0.14.1",
2169 "gix-testtools",2168 "gix-testtools",
2169 "gix-utils 0.1.9",
2170 "gix-validate 0.8.3",2170 "gix-validate 0.8.3",
2171 "itoa",2171 "itoa",
2172 "pretty_assertions",2172 "pretty_assertions",
@@ -2347,7 +2347,6 @@ dependencies = [ · 174 unmodified lines
2347 "async-std",2347 "async-std",
2348 "async-trait",2348 "async-trait",
2349 "bstr",2349 "bstr",
2350 "btoi",
2351 "document-features",2350 "document-features",
2352 "futures-io",2351 "futures-io",
2353 "futures-lite 2.1.0",2352 "futures-lite 2.1.0",
@@ -2358,6 +2357,7 @@ dependencies = [ · 4 unmodified lines
2358 "gix-packetline",2357 "gix-packetline",
2359 "gix-testtools",2358 "gix-testtools",
2360 "gix-transport",2359 "gix-transport",
2360 "gix-utils 0.1.9",
2361 "maybe-async",2361 "maybe-async",
2362 "serde",2362 "serde",
2363 "thiserror",2363 "thiserror",
@@ -2380,7 +2380,7 @@ name = "gix-quote" · 16 unmodified lines
2380version = "0.4.10"2380version = "0.4.10"
2381dependencies = [2381dependencies = [
2382 "bstr",2382 "bstr",
2383 "btoi",2383 "gix-utils 0.1.9",
2384 "thiserror",2384 "thiserror",
2385]2385]
2386 2386
gix-actor/Cargo.toml+9−4
@@ -19,19 +19,24 @@ serde = ["dep:serde", "bstr/serde", "gix-date/serde"] · 18 unmodified lines
19[dependencies]19[dependencies]
20gix-features = { version = "^0.38.0", path = "../gix-features", optional = true }20gix-features = { version = "^0.38.0", path = "../gix-features", optional = true }
21gix-date = { version = "^0.8.3", path = "../gix-date" }21gix-date = { version = "^0.8.3", path = "../gix-date" }
22gix-utils = { version = "^0.1.9", path = "../gix-utils" }
22 23
23thiserror = "1.0.38"24thiserror = "1.0.38"
24btoi = "0.4.2"25bstr = { version = "1.3.0", default-features = false, features = [
25bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"]}26 "std",
27 "unicode",
28] }
26winnow = { version = "0.6.0", features = ["simd"] }29winnow = { version = "0.6.0", features = ["simd"] }
27itoa = "1.0.1"30itoa = "1.0.1"
28serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}31serde = { version = "1.0.114", optional = true, default-features = false, features = [
32 "derive",
33] }
29 34
30document-features = { version = "0.2.0", optional = true }35document-features = { version = "0.2.0", optional = true }
31 36
32[dev-dependencies]37[dev-dependencies]
33pretty_assertions = "1.0.0"38pretty_assertions = "1.0.0"
34gix-testtools = { path = "../tests/tools"}39gix-testtools = { path = "../tests/tools" }
35gix-hash = { path = "../gix-hash" }40gix-hash = { path = "../gix-hash" }
36 41
37[package.metadata.docs.rs]42[package.metadata.docs.rs]
gix-actor/src/signature/decode.rs+1−1
@@ -1,7 +1,7 @@
1pub(crate) mod function {1pub(crate) mod function {
2 use bstr::ByteSlice;2 use bstr::ByteSlice;
3 use btoi::btoi;
4 use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};3 use gix_date::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};
4 use gix_utils::btoi::btoi;
5 use winnow::{5 use winnow::{
6 combinator::{alt, separated_pair, terminated},6 combinator::{alt, separated_pair, terminated},
7 error::{AddContext, ParserError, StrContext},7 error::{AddContext, ParserError, StrContext},
gix-index/Cargo.toml+12−4
@@ -20,13 +20,17 @@ test = true · 19 unmodified lines
20serde = ["dep:serde", "smallvec/serde", "gix-hash/serde"]20serde = ["dep:serde", "smallvec/serde", "gix-hash/serde"]
21 21
22[dependencies]22[dependencies]
23gix-features = { version = "^0.38.0", path = "../gix-features", features = ["rustsha1", "progress"] }23gix-features = { version = "^0.38.0", path = "../gix-features", features = [
24 "rustsha1",
25 "progress",
26] }
24gix-hash = { version = "^0.14.1", path = "../gix-hash" }27gix-hash = { version = "^0.14.1", path = "../gix-hash" }
25gix-bitmap = { version = "^0.2.10", path = "../gix-bitmap" }28gix-bitmap = { version = "^0.2.10", path = "../gix-bitmap" }
26gix-object = { version = "^0.41.0", path = "../gix-object" }29gix-object = { version = "^0.41.0", path = "../gix-object" }
27gix-traverse = { version = "^0.37.0", path = "../gix-traverse" }30gix-traverse = { version = "^0.37.0", path = "../gix-traverse" }
28gix-lock = { version = "^13.0.0", path = "../gix-lock" }31gix-lock = { version = "^13.0.0", path = "../gix-lock" }
29gix-fs = { version = "^0.10.0", path = "../gix-fs" }32gix-fs = { version = "^0.10.0", path = "../gix-fs" }
33gix-utils = { version = "^0.1.9", path = "../gix-utils" }
30 34
31hashbrown = "0.14.3"35hashbrown = "0.14.3"
32fnv = "1.0.7"36fnv = "1.0.7"
@@ -35,16 +39,20 @@ memmap2 = "0.9.0" · 2 unmodified lines
35filetime = "0.2.15"39filetime = "0.2.15"
36bstr = { version = "1.3.0", default-features = false }40bstr = { version = "1.3.0", default-features = false }
37 41
38serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }42serde = { version = "1.0.114", optional = true, default-features = false, features = [
43 "derive",
44] }
39smallvec = "1.7.0"45smallvec = "1.7.0"
40btoi = "0.4.3"
41itoa = "1.0.3"46itoa = "1.0.3"
42bitflags = "2"47bitflags = "2"
43 48
44document-features = { version = "0.2.0", optional = true }49document-features = { version = "0.2.0", optional = true }
45 50
46[target.'cfg(not(windows))'.dependencies]51[target.'cfg(not(windows))'.dependencies]
47rustix = { version = "0.38.20", default-features = false, features = ["std", "fs"] }52rustix = { version = "0.38.20", default-features = false, features = [
53 "std",
54 "fs",
55] }
48libc = { version = "0.2.149" }56libc = { version = "0.2.149" }
49 57
50[package.metadata.docs.rs]58[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
22 let (path, data) = split_at_byte_exclusive(data, 0)?;22 let (path, data) = split_at_byte_exclusive(data, 0)?;
23 23
24 let (entry_count, data) = split_at_byte_exclusive(data, b' ')?;24 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()?;
26 26
27 let (subtree_count, data) = split_at_byte_exclusive(data, b'\n')?;27 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()?;
29 29
30 let (id, mut data) = if num_entries >= 0 {30 let (id, mut data) = if num_entries >= 0 {
31 let (hash, data) = split_at_pos(data, hash_len)?;31 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
20 20
21[features]21[features]
22## Data structures implement `serde::Serialize` and `serde::Deserialize`.22## Data structures implement `serde::Serialize` and `serde::Deserialize`.
23serde = ["dep:serde", "bstr/serde", "smallvec/serde", "gix-hash/serde", "gix-actor/serde"]23serde = [
24 "dep:serde",
25 "bstr/serde",
26 "smallvec/serde",
27 "gix-hash/serde",
28 "gix-actor/serde",
29]
24## When parsing objects by default errors will only be available on the granularity of success or failure, and with the above flag enabled30## When parsing objects by default errors will only be available on the granularity of success or failure, and with the above flag enabled
25## details information about the error location will be collected.31## details information about the error location will be collected.
26## Use it in applications which expect broken or invalid objects or for debugging purposes. Incorrectly formatted objects aren't at all32## 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
28verbose-object-parsing-errors = ["winnow/std"]34verbose-object-parsing-errors = ["winnow/std"]
29 35
30[dependencies]36[dependencies]
31gix-features = { version = "^0.38.0", path = "../gix-features", features = ["rustsha1", "progress"] }37gix-features = { version = "^0.38.0", path = "../gix-features", features = [
38 "rustsha1",
39 "progress",
40] }
32gix-hash = { version = "^0.14.1", path = "../gix-hash" }41gix-hash = { version = "^0.14.1", path = "../gix-hash" }
33gix-validate = { version = "^0.8.3", path = "../gix-validate" }42gix-validate = { version = "^0.8.3", path = "../gix-validate" }
34gix-actor = { version = "^0.30.0", path = "../gix-actor" }43gix-actor = { version = "^0.30.0", path = "../gix-actor" }
35gix-date = { version = "^0.8.3", path = "../gix-date" }44gix-date = { version = "^0.8.3", path = "../gix-date" }
45gix-utils = { version = "^0.1.9", path = "../gix-utils" }
36 46
37btoi = "0.4.2"
38itoa = "1.0.1"47itoa = "1.0.1"
39thiserror = "1.0.34"48thiserror = "1.0.34"
40bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }49bstr = { version = "1.3.0", default-features = false, features = [
50 "std",
51 "unicode",
52] }
41winnow = { version = "0.6.0", features = ["simd"] }53winnow = { version = "0.6.0", features = ["simd"] }
42smallvec = { version = "1.4.0", features = ["write"] }54smallvec = { version = "1.4.0", features = ["write"] }
43serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}55serde = { version = "1.0.114", optional = true, default-features = false, features = [
56 "derive",
57] }
44 58
45document-features = { version = "0.2.0", optional = true }59document-features = { version = "0.2.0", optional = true }
46 60
47[dev-dependencies]61[dev-dependencies]
48criterion = "0.5.1"62criterion = "0.5.1"
49pretty_assertions = "1.0.0"63pretty_assertions = "1.0.0"
50gix-testtools = { path = "../tests/tools"}64gix-testtools = { path = "../tests/tools" }
51 65
52[package.metadata.docs.rs]66[package.metadata.docs.rs]
53all-features = true67all-features = true
54features = ["document-features"]68features = ["document-features"]
55
gix-object/src/lib.rs+2−2
@@ -358,7 +358,7 @@ pub mod decode { · 357 unmodified lines
358 pub enum LooseHeaderDecodeError {358 pub enum LooseHeaderDecodeError {
359 #[error("{message}: {number:?}")]359 #[error("{message}: {number:?}")]
360 ParseIntegerError {360 ParseIntegerError {
361 source: btoi::ParseIntegerError,361 source: gix_utils::btoi::ParseIntegerError,
362 message: &'static str,362 message: &'static str,
363 number: bstr::BString,363 number: bstr::BString,
364 },364 },
@@ -383,7 +383,7 @@ pub mod decode { · 18 unmodified lines
383 message: "Did not find 0 byte in header",383 message: "Did not find 0 byte in header",
384 })?;384 })?;
385 let size_bytes = &input[kind_end + 1..size_end];385 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 {
387 source,387 source,
388 message: "Object size in header could not be parsed",388 message: "Object size in header could not be parsed",
389 number: size_bytes.into(),389 number: size_bytes.into(),
gix-protocol/Cargo.toml+18−6
@@ -23,7 +23,12 @@ doctest = false · 22 unmodified lines
23## If set, blocking command implementations are available and will use the blocking version of the `gix-transport` crate.23## If set, blocking command implementations are available and will use the blocking version of the `gix-transport` crate.
24blocking-client = ["gix-transport/blocking-client", "maybe-async/is_sync"]24blocking-client = ["gix-transport/blocking-client", "maybe-async/is_sync"]
25## As above, but provides async implementations instead.25## As above, but provides async implementations instead.
26async-client = ["gix-transport/async-client", "async-trait", "futures-io", "futures-lite"]26async-client = [
27 "gix-transport/async-client",
28 "async-trait",
29 "futures-io",
30 "futures-lite",
31]
27 32
28#! ### Other33#! ### Other
29## Data structures implement `serde::Serialize` and `serde::Deserialize`.34## Data structures implement `serde::Serialize` and `serde::Deserialize`.
@@ -40,17 +45,24 @@ path = "tests/async-protocol.rs" · 10 unmodified lines
40required-features = ["async-client"]45required-features = ["async-client"]
41 46
42[dependencies]47[dependencies]
43gix-features = { version = "^0.38.0", path = "../gix-features", features = ["progress"] }48gix-features = { version = "^0.38.0", path = "../gix-features", features = [
49 "progress",
50] }
44gix-transport = { version = "^0.41.0", path = "../gix-transport" }51gix-transport = { version = "^0.41.0", path = "../gix-transport" }
45gix-hash = { version = "^0.14.1", path = "../gix-hash" }52gix-hash = { version = "^0.14.1", path = "../gix-hash" }
46gix-date = { version = "^0.8.3", path = "../gix-date" }53gix-date = { version = "^0.8.3", path = "../gix-date" }
47gix-credentials = { version = "^0.24.0", path = "../gix-credentials" }54gix-credentials = { version = "^0.24.0", path = "../gix-credentials" }
55gix-utils = { version = "^0.1.9", path = "../gix-utils" }
48 56
49thiserror = "1.0.32"57thiserror = "1.0.32"
50serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}58serde = { version = "1.0.114", optional = true, default-features = false, features = [
51bstr = { version = "1.3.0", default-features = false, features = ["std", "unicode"] }59 "derive",
60] }
61bstr = { version = "1.3.0", default-features = false, features = [
62 "std",
63 "unicode",
64] }
52winnow = { version = "0.6.0", features = ["simd"] }65winnow = { version = "0.6.0", features = ["simd"] }
53btoi = "0.4.2"
54 66
55# for async-client67# for async-client
56async-trait = { version = "0.1.51", optional = true }68async-trait = { version = "0.1.51", optional = true }
@@ -62,7 +74,7 @@ document-features = { version = "0.2.0", optional = true } · 5 unmodified lines
62 74
63[dev-dependencies]75[dev-dependencies]
64async-std = { version = "1.9.0", features = ["attributes"] }76async-std = { version = "1.9.0", features = ["attributes"] }
65gix-packetline = { path = "../gix-packetline" ,version = "^0.17.3" }77gix-packetline = { path = "../gix-packetline", version = "^0.17.3" }
66gix-testtools = { path = "../tests/tools" }78gix-testtools = { path = "../tests/tools" }
67 79
68[package.metadata.docs.rs]80[package.metadata.docs.rs]
gix-protocol/src/remote_progress.rs+1−1
@@ -75,7 +75,7 @@ impl<'a> RemoteProgress<'a> { · 74 unmodified lines
75 75
76fn parse_number(i: &mut &[u8]) -> PResult<usize, ()> {76fn parse_number(i: &mut &[u8]) -> PResult<usize, ()> {
77 take_till(0.., |c: u8| !c.is_ascii_digit())77 take_till(0.., |c: u8| !c.is_ascii_digit())
78 .try_map(btoi::btoi)78 .try_map(gix_utils::btoi::btoi)
79 .parse_next(i)79 .parse_next(i)
80}80}
81 81
gix-quote/Cargo.toml+3−2
@@ -13,6 +13,7 @@ include = ["src/**/*", "LICENSE-*"] · 12 unmodified lines
13doctest = false13doctest = false
14 14
15[dependencies]15[dependencies]
16bstr = { version = "1.3.0", default-features = false, features = ["std"]}16gix-utils = { version = "^0.1.9", path = "../gix-utils" }
17
18bstr = { version = "1.3.0", default-features = false, features = ["std"] }
17thiserror = "1.0.38"19thiserror = "1.0.38"
18btoi = "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
89 })?89 })?
90 .read_exact(&mut buf[1..])90 .read_exact(&mut buf[1..])
91 .expect("impossible to fail as numbers match");91 .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))?;
93 out.push(byte);94 out.push(byte);
94 input = &input[2..];95 input = &input[2..];
95 consumed += 2;96 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)]
11pub struct ParseIntegerError {
12 kind: ErrorKind,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16enum ErrorKind {
17 Empty,
18 InvalidDigit,
19 Overflow,
20 Underflow,
21}
22
23impl 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
34impl 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
40impl 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
46pub 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
59impl 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
81impl 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
103impl 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
125impl 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
147impl 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]
195pub 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
224pub 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
303pub 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
339fn 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
13///13///
14pub mod str;14pub mod str;
15 15
16///
17pub mod btoi;
18
16/// A utility to do buffer-swapping with.19/// A utility to do buffer-swapping with.
17///20///
18/// Use `src` to read from and `dest` to write to, and after actually changing data, call [Buffers::swap()].21/// Use `src` to read from and `dest` to write to, and after actually changing data, call [Buffers::swap()].