use super::value::{Map, Number, Value};
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
pub fn hex(octets: &[u8]) -> String {
let digit = |half: u8| half + b'0' + u8::from(half > 9) * (b'a' - b'9' - 1);
let mut text = vec![0; octets.len() * 2];
let (pairs, _) = text.as_chunks_mut::<2>();
for (pair, octet) in pairs.iter_mut().zip(octets) {
*pair = [digit(octet >> 4), digit(octet & 15)];
}
String::from_utf8(text).unwrap_or_default()
}
pub fn unhex(text: &str) -> Option<Vec<u8>> {
const NONE: u8 = 0x80;
let digit = |digit: u8| {
let (number, letter) = (digit.wrapping_sub(b'0'), (digit | 0x20).wrapping_sub(b'a'));
match (number < 10, letter < 6) {
(true, _) => number,
(false, true) => letter + 10,
(false, false) => NONE,
}
};
let (pairs, rest) = text.as_bytes().as_chunks::<2>();
if !rest.is_empty() {
return None;
}
let mut octets = vec![0; pairs.len()];
let mut other = 0;
for (octet, [high, low]) in octets.iter_mut().zip(pairs) {
let (high, low) = (digit(*high), digit(*low));
other |= high | low;
*octet = high << 4 | low & 15;
}
(other & NONE == 0).then_some(octets)
}
fn letters(name: &str) -> impl Iterator<Item = u8> + '_ {
let letters = name.bytes().filter(u8::is_ascii_alphanumeric);
letters.map(|letter| letter.to_ascii_lowercase())
}
pub fn same_name(a: &str, b: &str) -> bool {
letters(a).next().is_some() && letters(a).eq(letters(b))
}
fn alike(a: &str, b: &str) -> bool {
let separator = |c: u8| matches!(c, b'-' | b'_' | b' ');
a.len() == b.len()
&& (a.bytes().zip(b.bytes()))
.all(|(a, b)| a.eq_ignore_ascii_case(&b) || (separator(a) && separator(b)))
}
pub fn named<T: AsRef<str>>(names: &[T], name: &str) -> Option<usize> {
let position = |is: &dyn Fn(&str) -> bool| names.iter().position(|known| is(known.as_ref()));
position(&|known| known == name)
.or_else(|| position(&|known| alike(known, name)))
.or_else(|| position(&|known| same_name(known, name)))
}
pub fn nearest<'n, T: AsRef<str>>(names: &'n [T], name: &str, most: usize) -> Vec<&'n str> {
let written: Vec<u8> = letters(name).collect();
let farthest = (written.len() / 3).max(2);
let mut near: Vec<(usize, usize, &str)> = names
.iter()
.enumerate()
.filter_map(|(position, known)| {
let known = known.as_ref();
let distance = distance(&written, &letters(known).collect::<Vec<_>>());
(distance <= farthest).then_some((distance, position, known))
})
.collect();
near.sort_unstable();
near.into_iter().take(most).map(|(.., name)| name).collect()
}
fn distance(a: &[u8], b: &[u8]) -> usize {
let mut row: Vec<usize> = (0..=b.len()).collect();
for (i, x) in a.iter().enumerate() {
let mut diagonal = row[0];
row[0] = i + 1;
for (j, y) in b.iter().enumerate() {
let above = row[j + 1];
row[j + 1] = match x == y {
true => diagonal,
false => 1 + diagonal.min(above).min(row[j]),
};
diagonal = above;
}
}
row[b.len()]
}
pub(crate) fn key<'m>(members: &'m Map, name: &str) -> Option<&'m str> {
if let Some((known, _)) = members.get_key_value(name) {
return Some(known);
}
let alike = members.keys().find(|known| alike(known, name));
let found = alike.or_else(|| members.keys().find(|known| same_name(known, name)));
found.map(String::as_str)
}
pub fn unsigned(value: u64) -> Value {
Value::Number(Number::from(value))
}
pub fn signed(value: i64) -> Value {
Value::Number(Number::from(value))
}
pub fn octets(octets: &[u8]) -> Value {
Value::String(hex(octets))
}
pub fn text(text: &str) -> Value {
Value::String(text.to_string())
}
pub fn bits(octets: &[u8], length: usize) -> Value {
let mut members = Map::new();
members.insert("value".into(), Value::String(hex(octets)));
members.insert("length".into(), Value::Number(Number::from(length)));
Value::Object(members)
}
pub fn alternative(name: &str, value: Value) -> Value {
let mut members = Map::new();
members.insert(name.to_string(), value);
Value::Object(members)
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
Plain,
Octets,
Named(&'static [&'static str]),
}
pub fn written_as(form: Form, written: &Value) -> Value {
match (form, written) {
(Form::Octets, Value::String(text)) if unhex(text).is_some() => {
Value::String(text.to_ascii_lowercase())
}
(Form::Named(names), Value::String(text)) => match named(names, text) {
Some(position) => Value::String(names[position].to_string()),
None => written.clone(),
},
(form, Value::Array(entries)) => Value::Array(
entries
.iter()
.map(|entry| written_as(form, entry))
.collect(),
),
_ => written.clone(),
}
}