This commit is contained in:
phyto 2023-02-13 04:31:35 +00:00
commit 017fa39dc1
Signed by: phyto
SSH Key Fingerprint: SHA256:FJdIUDW+Q/c/oLlaNI7vrxCrv0VMxMgT6usJ+7A/wo0
4 changed files with 180 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "hex"
version = "0.1.0"

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "hex"
version = "0.1.0"
authors = ["phyto <phyto@disroot.org>"]
edition = "2021"
description = "A hexadecimal (de)serialiser"
license = "AGPL-3.0-or-later"

164
src/lib.rs Normal file
View File

@ -0,0 +1,164 @@
#![warn(clippy::nursery)]
#![warn(clippy::panic_in_result_fn)]
fn unsigned_to_hex(mut x: u128) -> Option<String> {
let mut r = String::new();
while x > 0 {
let digit = x % 0x10;
r.push(int_to_char(digit as u8)?);
x /= 0x10;
}
Some(r.chars().rev().collect())
}
fn unsigned_from_hex(strn: &str) -> Option<u128> {
let mut r = 0;
let mut inp_str = strn.chars().rev().collect::<String>();
while !inp_str.is_empty() {
let digit = char_to_int(inp_str.pop().expect("we just checked it's not empty"))?;
r <<= 4;
r += u128::from(digit);
}
Some(r)
}
pub fn to_hex(x: i128) -> Option<String> {
let mut r = unsigned_to_hex(x.unsigned_abs())?;
if x < 0 {
r.insert(0, '-');
}
Some(r)
}
/// Convert a hexadecimal string into a signed integer.
/// A `-` for a negative can be before or after the optional `0x`.
/// # Example
/// ```
/// use hex::from_hex;
/// assert_eq!( from_hex("e"), Some(14) );
/// assert_eq!( from_hex("0xA3"), Some(163) );
/// assert_eq!( from_hex("-0xabCdef"), Some(-11259375) );
/// assert_eq!( from_hex("-abcdef"), Some(-11259375) );
/// assert_eq!( from_hex("0x-ABCDEF"), from_hex("-0xabcdef") );
///
/// assert_eq!( from_hex("abcdefG"), None );
/// assert_eq!( from_hex("ab cd ef"), None );
/// assert_eq!( from_hex(" 0011"), None );
///
/// ```
pub fn from_hex(mut strn: &str) -> Option<i128> {
let mut is_positive = true;
{
if let Some(s) = strn.strip_prefix('-') {
strn = s;
is_positive = false;
}
if let Some(s) = strn.strip_prefix("0x-") {
strn = s;
is_positive = false;
}
}
let mut r = unsigned_from_hex(strn)? as i128;
if !is_positive {
r = -r;
}
Some(r)
}
#[cfg(test)]
mod _test {
#[test]
fn to_hex() {
use super::*;
let pairs = [("3039", 12345), ("4", 4), ("cffee", 851_950), ("-ae", -174)];
for (lhs, rhs) in pairs {
println!("{lhs} == {rhs} ?");
assert_eq!(lhs.to_string(), to_hex(rhs).unwrap());
}
}
#[test]
fn from_hex() {
use super::*;
let pairs = [("3039", 12345), ("4", 4), ("cffee", 851_950), ("-ae", -174)];
println!("Hex == Deniary ?");
for (lhs, rhs) in pairs {
println!("{lhs} == {rhs} ?");
assert_eq!(from_hex(lhs).unwrap(), rhs);
}
}
}
const fn char_to_int(chr: char) -> Option<u8> {
const UNCONVERTABLE: u8 = core::u8::MAX;
let r = match chr.to_ascii_lowercase() {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' => 10,
'b' => 11,
'c' => 12,
'd' => 13,
'e' => 14,
'f' => 15,
_ => UNCONVERTABLE,
};
if r == UNCONVERTABLE {
return None;
}
Some(r)
}
const fn int_to_char(i: u8) -> Option<char> {
const UNCONVERTABLE: char = '\0';
let r = match i {
0 => '0',
1 => '1',
2 => '2',
3 => '3',
4 => '4',
5 => '5',
6 => '6',
7 => '7',
8 => '8',
9 => '9',
10 => 'a',
11 => 'b',
12 => 'c',
13 => 'd',
14 => 'e',
15 => 'f',
_ => UNCONVERTABLE,
};
if r == UNCONVERTABLE {
return None;
}
Some(r)
}