use crate::*;
use core::convert::{TryFrom, TryInto};
use core::fmt;
use core::fmt::{Debug, Display, Formatter};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
#[repr(transparent)]
pub struct HamChar(u8);
impl HamChar {
pub const NUL: HamChar = HamChar(0);
pub const ESC: HamChar = HamChar(39);
pub const fn from_ascii_byte(c: u8) -> Option<HamChar> {
match c {
b'\x00' => Some(HamChar::NUL),
b'A'..=b'Z' => Some(HamChar(c - b'A' + 1)),
b'a'..=b'z' => Some(HamChar(c - b'a' + 1)),
b'0'..=b'9' => Some(HamChar(c - b'0' + 27)),
b'/' => Some(HamChar(37)),
b'-' => Some(HamChar(38)),
b'^' => Some(HamChar::ESC),
_ => None,
}
}
pub const fn from_char(c: char) -> Option<HamChar> {
let c = c as u32;
if c < 128 {
Self::from_ascii_byte(c.to_le_bytes()[0])
} else {
None
}
}
pub const fn to_ascii_byte(&self) -> u8 {
const CHARS: &'static [u8] = b"\x00ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-^";
CHARS[self.0 as usize]
}
pub const fn to_char(&self) -> char {
const ALT_NUL: char = '␀';
if self.is_nul() {
ALT_NUL
} else {
self.to_ascii_byte() as char
}
}
pub const fn index(&self) -> u8 {
self.0
}
pub const fn is_nul(&self) -> bool {
self.0 == Self::NUL.0
}
pub const fn is_esc(&self) -> bool {
self.0 == Self::ESC.0
}
pub fn try_apply_eui_hack(self) -> Option<Self> {
match self {
HamChar(0) => Some(HamChar(0)),
HamChar(28) => Some(HamChar(8)),
HamChar(29) => Some(HamChar(16)),
HamChar(30) => Some(HamChar(24)),
HamChar(31) => Some(HamChar(32)),
_ => None,
}
}
pub fn try_reverse_eui_hack(self) -> Option<Self> {
match self {
HamChar(0) => Some(HamChar(0)),
HamChar(8) => Some(HamChar(28)),
HamChar(16) => Some(HamChar(29)),
HamChar(24) => Some(HamChar(30)),
HamChar(32) => Some(HamChar(31)),
_ => None,
}
}
}
impl TryFrom<char> for HamChar {
type Error = InvalidChar;
fn try_from(value: char) -> core::result::Result<Self, Self::Error> {
HamChar::from_char(value).ok_or(InvalidChar)
}
}
impl TryFrom<u8> for HamChar {
type Error = InvalidChar;
fn try_from(value: u8) -> core::result::Result<Self, Self::Error> {
HamChar::from_ascii_byte(value).ok_or(InvalidChar)
}
}
impl From<HamChar> for char {
fn from(value: HamChar) -> Self {
value.to_char()
}
}
impl Display for HamChar {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_char(), f)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub(crate) struct HamCharChunk(pub [HamChar; 3]);
impl HamCharChunk {
pub const fn len(self) -> usize {
if self.0[0].is_nul() {
return 0;
}
if self.0[1].is_nul() {
return 1;
}
if self.0[2].is_nul() {
return 2;
}
return 3;
}
pub fn try_apply_eui_hack(self) -> Option<Self> {
Some(HamCharChunk([
self.0[0],
self.0[1],
self.0[2].try_apply_eui_hack()?,
]))
}
pub fn try_reverse_eui_hack(self) -> Option<Self> {
Some(HamCharChunk([
self.0[0],
self.0[1],
self.0[2].try_reverse_eui_hack()?,
]))
}
}
impl From<HamCharChunk> for u16 {
fn from(chunk: HamCharChunk) -> Self {
(chunk.0[0].0 as u16) * 1600 + (chunk.0[1].0 as u16) * 40 + (chunk.0[2].0 as u16)
}
}
impl TryFrom<u16> for HamCharChunk {
type Error = InvalidChunk;
fn try_from(chunk: u16) -> Result<Self, Self::Error> {
if chunk == 0 || (chunk >= 0x0640 && chunk <= 0xF9FF) {
Ok(HamCharChunk([
HamChar((chunk / 1600u16 % 40u16).try_into().unwrap()),
HamChar((chunk / 40u16 % 40u16).try_into().unwrap()),
HamChar((chunk % 40u16).try_into().unwrap()),
]))
} else {
Err(InvalidChunk)
}
}
}
impl TryFrom<[char; 3]> for HamCharChunk {
type Error = InvalidCharAt;
fn try_from(chunk: [char; 3]) -> Result<Self, Self::Error> {
Ok(HamCharChunk([
chunk[0].try_into().map_err(|_| InvalidCharAt(0))?,
chunk[1].try_into().map_err(|_| InvalidCharAt(1))?,
chunk[2].try_into().map_err(|_| InvalidCharAt(2))?,
]))
}
}
impl Display for HamCharChunk {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() || !self.0[2].is_nul() {
write!(f, "{}{}{}", self.0[0], self.0[1], self.0[2])
} else if !self.0[1].is_nul() {
write!(f, "{}{}", self.0[0], self.0[1])
} else if !self.0[0].is_nul() {
write!(f, "{}", self.0[0])
} else {
Ok(())
}
}
}