#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::convert::TryFrom;
use std::fmt;
#[derive(Clone)]
pub enum CbmString {
Ascii(AsciiString),
Petscii(PetsciiString),
}
impl fmt::Display for CbmString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CbmString::Ascii(ascii) => write!(f, "{}", ascii),
CbmString::Petscii(petscii) => write!(f, "{}", petscii),
}
}
}
impl CbmString {
pub fn to_petscii(&self) -> PetsciiString {
match self {
CbmString::Ascii(ascii) => ascii.into(),
CbmString::Petscii(petscii) => petscii.clone(),
}
}
pub fn from_petscii_bytes(bytes: &[u8]) -> Self {
CbmString::Petscii(PetsciiString::from_petscii_bytes(bytes))
}
pub fn from_ascii_bytes(bytes: &[u8]) -> Self {
CbmString::Ascii(AsciiString::from_bytes(bytes).unwrap())
}
}
impl From<AsciiString> for CbmString {
fn from(ascii: AsciiString) -> Self {
CbmString::Ascii(ascii)
}
}
impl From<PetsciiString> for CbmString {
fn from(petscii: PetsciiString) -> Self {
CbmString::Petscii(petscii)
}
}
impl<'a> TryFrom<&'a str> for CbmString {
type Error = Box<dyn std::error::Error>;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
if let Ok(ascii) = AsciiString::try_from(s) {
Ok(CbmString::Ascii(ascii))
} else {
Ok(CbmString::Petscii(PetsciiString::from_petscii_bytes(
s.as_bytes(),
)))
}
}
}
#[derive(Debug, Clone)]
pub struct PetsciiString(Vec<u8>);
#[derive(Debug, Clone)]
pub struct AsciiString(Vec<u8>);
impl PetsciiString {
pub unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
PetsciiString(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
Some(PetsciiString(bytes.to_vec()))
}
pub fn to_ascii(&self) -> AsciiString {
let converted: Vec<u8> = self.0.iter().map(|&c| petscii_to_ascii(c) as u8).collect();
AsciiString(converted)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl AsciiString {
pub unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
AsciiString(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.iter().all(|&b| b.is_ascii()) {
Some(AsciiString(bytes.to_vec()))
} else {
None
}
}
pub fn to_petscii(&self) -> PetsciiString {
trace!("Converting ASCII to PETSCII - starting with {}", self);
let converted: Vec<u8> = self
.0
.iter()
.map(|&c| ascii_to_petscii(c as char))
.collect();
trace!("Ending with {:?}", converted);
PetsciiString(converted)
}
pub fn to_string(&self) -> String {
unsafe { String::from_utf8_unchecked(self.0.clone()) }
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for PetsciiString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl fmt::Display for AsciiString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = unsafe { std::str::from_utf8_unchecked(&self.0) };
write!(f, "{}", s)
}
}
impl PartialEq for PetsciiString {
fn eq(&self, other: &Self) -> bool {
self.to_ascii().0 == other.to_ascii().0
}
}
impl Eq for PetsciiString {}
impl PartialEq for AsciiString {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for AsciiString {}
impl PartialEq<AsciiString> for PetsciiString {
fn eq(&self, other: &AsciiString) -> bool {
self.to_ascii().0 == other.0
}
}
impl PartialEq<PetsciiString> for AsciiString {
fn eq(&self, other: &PetsciiString) -> bool {
self.0 == other.to_ascii().0
}
}
impl From<AsciiString> for PetsciiString {
fn from(ascii: AsciiString) -> Self {
ascii.to_petscii()
}
}
impl From<PetsciiString> for AsciiString {
fn from(petscii: PetsciiString) -> Self {
petscii.to_ascii()
}
}
impl From<&AsciiString> for PetsciiString {
fn from(ascii: &AsciiString) -> Self {
ascii.to_petscii()
}
}
impl From<&PetsciiString> for AsciiString {
fn from(petscii: &PetsciiString) -> Self {
petscii.to_ascii()
}
}
impl From<AsciiString> for String {
fn from(ascii: AsciiString) -> String {
ascii.to_string()
}
}
impl AsciiString {
pub fn from_ascii_str(s: &str) -> Self {
Self::try_from(s).expect("String contains non-ASCII characters")
}
}
impl PetsciiString {
pub fn from_petscii_bytes(bytes: &[u8]) -> Self {
PetsciiString(bytes.to_vec())
}
pub fn from_ascii_str(s: &str) -> Self {
AsciiString::from_ascii_str(s).into()
}
}
impl TryFrom<String> for AsciiString {
type Error = &'static str;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.is_ascii() {
Ok(AsciiString(s.into_bytes()))
} else {
Err("String contains non-ASCII characters")
}
}
}
impl TryFrom<&String> for AsciiString {
type Error = &'static str;
fn try_from(s: &String) -> Result<Self, Self::Error> {
if s.is_ascii() {
Ok(AsciiString(s.as_bytes().to_vec()))
} else {
Err("String contains non-ASCII characters")
}
}
}
impl TryFrom<&str> for AsciiString {
type Error = &'static str;
fn try_from(s: &str) -> Result<Self, Self::Error> {
if s.is_ascii() {
Ok(AsciiString(s.as_bytes().to_vec()))
} else {
Err("String contains non-ASCII characters")
}
}
}
fn petscii_to_ascii(character: u8) -> char {
match character {
0x0a | 0x0d => '\n',
0x00..=0x09 | 0x0b..=0x0c | 0x0e..=0x1f => '.', 0x20..=0x40 => character as char, 0x41..=0x5A => (character | 0x20) as char, 0x5B..=0x7F => character as char, 0x80..=0xC0 => '.', 0xC1..=0xDA => (character ^ 0x80) as char, 0xDB..=0xFF => '.', }
}
fn ascii_to_petscii(character: char) -> u8 {
let c = character as u8;
match c {
0x5b..=0x7e => c ^ 0x20,
0x41..=0x5A => c | 0x80,
_ => c,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ascii_string_conversion() {
let ascii = AsciiString::try_from("Hello").unwrap();
let petscii = ascii.to_petscii();
let back_to_ascii = petscii.to_ascii();
assert_eq!(ascii, back_to_ascii);
}
#[test]
fn test_ascii_validation() {
assert!(AsciiString::try_from("Hello").is_ok());
assert!(AsciiString::try_from("Hello 🌍").is_err());
}
#[test]
fn test_display() {
let ascii = AsciiString::try_from("Hello").unwrap();
let petscii = ascii.to_petscii();
assert_eq!(&format!("{}", ascii), "Hello");
assert_eq!(
&format!("{}", petscii),
"PetsciiString([200, 69, 76, 76, 79])"
);
}
#[test]
fn test_equality() {
let ascii1 = AsciiString::try_from("TEST").unwrap();
let petscii1 = ascii1.to_petscii();
let ascii2 = AsciiString::try_from("TEST").unwrap();
let petscii2 = ascii2.to_petscii();
assert_eq!(ascii1, ascii2);
assert_eq!(petscii1, petscii2);
assert_eq!(ascii1, petscii1);
assert_eq!(petscii1, ascii1);
let different = AsciiString::try_from("OTHER").unwrap();
assert_ne!(ascii1, different);
assert_ne!(petscii1, different);
assert_ne!(petscii1, different.to_petscii());
}
}