pkstrings: Move PKString and tests in their own modules

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2021-11-14 15:44:51 +01:00
parent 025828debc
commit 5900325086
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
3 changed files with 331 additions and 212 deletions

View file

@ -13,218 +13,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::convert::TryFrom;
use std::fmt;
use std::ops::Deref;
const fn strtohex(chr: &char) -> Option<u8> {
Some(match chr {
cap @ 'A'..='Z' => 0x80 - ('A' as u8) + (*cap as u8),
'(' => 0x9a,
')' => 0x9b,
':' => 0x9c,
';' => 0x9d,
'[' => 0x9e,
']' => 0x9f,
low @ 'a'..='z' => 0xa0 - ('a' as u8) + (*low as u8),
'\'' => 0xe0,
'-' => 0xe3,
'?' => 0xe6,
'!' => 0xe7,
'.' => 0xe8,
'▷' => 0xec,
'▶' => 0xed,
'▼' => 0xee,
'♂' => 0xef,
'×' => 0xf1,
// TODO: Pattern currently unreachable. Figure something out.
// In the Japanese games (as can be seen below), 0xF2 is distinguishable from 0xE8, with
// the former meant as a decimal point while the latter is punctuation. Presumably this
// intention was largely inherited when the English games were made, as most of the game's
// script uses 0xE8 exclusively; however, 0xF2 appears in the character table for user
// input, meaning it may appear in user-input names (and, conversely, 0xE8 never should).
// Source: https://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_I
// '.' => 0xf2,
'/' => 0xf3,
',' => 0xf4,
'♀' => 0xf5,
num @ '0'..='9' => 0xf6 - ('0' as u8) + (*num as u8),
' ' => 0x7f,
'@' => 0x50,
_ => return None,
})
}
const fn hextostr(hex: u8) -> Option<char> {
Some(match hex {
cap @ 0x80..=0x99 => (('A' as u8) + (cap - 0x80)) as char,
0x9a => '(',
0x9b => ')',
0x9c => ':',
0x9d => ';',
0x9e => '[',
0x9f => ']',
low @ 0xa0..=0xb9 => (('a' as u8) + (low - 0xa0)) as char,
0xe0 => '\'',
0xe3 => '-',
0xe6 => '?',
0xe7 => '!',
0xe8 => '.',
0xec => '▷',
0xed => '▶',
0xee => '▼',
0xef => '♂',
0xf1 => '×',
0xf2 => '.',
0xf3 => '/',
0xf4 => ',',
0xf5 => '♀',
num @ 0xf6..=0xff => (('0' as u8) + (num - 0xf6)) as char,
0x7f => ' ',
0x50 => '@',
_ => return None,
})
}
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
InvalidCharacter,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Error::InvalidCharacter => write!(f, "Invalid character"),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PKString(String);
impl PKString {
pub fn into_string(self) -> String {
self.0
}
}
impl Deref for PKString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for PKString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}", &self.0))
}
}
impl TryFrom<u8> for PKString {
type Error = Error;
fn try_from(ord: u8) -> Result<PKString, Error> {
let mut buf = String::with_capacity(1);
match hextostr(ord) {
Some(chr) => buf.push(chr.clone()),
None => return Err(Error::InvalidCharacter),
}
Ok(PKString(buf))
}
}
impl TryFrom<&[u8]> for PKString {
type Error = Error;
fn try_from(data: &[u8]) -> Result<PKString, Error> {
let mut buf = String::with_capacity(data.len());
let placeholder = '_';
for ord in data {
if let Some(chr) = hextostr(*ord) {
buf.push(chr.clone());
} else {
buf.push(placeholder);
}
}
Ok(PKString(buf))
}
}
impl Into<Vec<u8>> for PKString {
fn into(self) -> Vec<u8> {
let mut vec = Vec::with_capacity(self.len());
for chr in self.0.chars() {
if let Some(ord) = strtohex(&chr) {
vec.push(ord);
} else {
// TODO: Change this.
panic!();
}
}
vec
}
}
impl TryFrom<&str> for PKString {
type Error = Error;
fn try_from(data: &str) -> Result<PKString, Error> {
PKString::try_from(String::from(data))
}
}
impl TryFrom<String> for PKString {
type Error = Error;
fn try_from(data: String) -> Result<PKString, Error> {
for chr in data.chars() {
if strtohex(&chr).is_none() {
return Err(Error::InvalidCharacter);
}
}
Ok(PKString(data))
}
}
pub mod pkstring;
#[cfg(test)]
mod tests {
use crate::PKString;
use std::convert::TryFrom;
mod tests;
#[test]
fn test_from_ord() {
let gary: &[u8] = &[0x86, 0x80, 0x91, 0x98];
assert_eq!(PKString::try_from(gary), Ok(PKString(String::from("GARY"))));
let party_nicks: &[u8] = &[
0x8a, 0x80, 0x83, 0x80, 0x81, 0x91, 0x80, 0x50, 0x50, 0x50, 0x50, 0x8d, 0x88, 0x83,
0x8e, 0x8a, 0x88, 0x8d, 0x86, 0x50, 0x50, 0x50, 0x81, 0x8b, 0x80, 0x92, 0x93, 0x8e,
0x88, 0x92, 0x84, 0x50, 0x50, 0x8e, 0x83, 0x83, 0x88, 0x92, 0x87, 0x50, 0x50, 0x50,
0x50, 0x50, 0x8f, 0x88, 0x83, 0x86, 0x84, 0x98, 0x50, 0x50, 0x50, 0x50, 0x50, 0x82,
0x87, 0x80, 0x91, 0x8c, 0x80, 0x8d, 0x83, 0x84, 0x91, 0x50,
];
let result =
String::from("KADABRA@@@@NIDOKING@@@BLASTOISE@@ODDISH@@@@@PIDGEY@@@@@CHARMANDER@");
assert_eq!(PKString::try_from(party_nicks), Ok(PKString(result)));
}
#[test]
fn test_from_chr() {
let gary: Vec<u8> = vec![0x86, 0x80, 0x91, 0x98];
let result: Vec<u8> = PKString(String::from("GARY")).into();
assert_eq!(result, gary);
let nidoranf: Vec<u8> = vec![0x8d, 0x88, 0x83, 0x8e, 0x91, 0x80, 0x8d, 0xef];
let result: Vec<u8> = PKString(String::from("NIDORAN♂")).into();
assert_eq!(result, nidoranf);
}
}
pub use pkstring::PKString;

208
pkstrings/src/pkstring.rs Normal file
View file

@ -0,0 +1,208 @@
// Copyright (C) 2021 "Maxime “pep” Buquet <pep@bouah.net>"
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
// for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::convert::TryFrom;
use std::fmt;
use std::ops::Deref;
const fn strtohex(chr: &char) -> Option<u8> {
Some(match chr {
cap @ 'A'..='Z' => 0x80 - ('A' as u8) + (*cap as u8),
'(' => 0x9a,
')' => 0x9b,
':' => 0x9c,
';' => 0x9d,
'[' => 0x9e,
']' => 0x9f,
low @ 'a'..='z' => 0xa0 - ('a' as u8) + (*low as u8),
'\'' => 0xe0,
'-' => 0xe3,
'?' => 0xe6,
'!' => 0xe7,
'.' => 0xe8,
'▷' => 0xec,
'▶' => 0xed,
'▼' => 0xee,
'♂' => 0xef,
'×' => 0xf1,
// TODO: Pattern currently unreachable. Figure something out.
// In the Japanese games (as can be seen below), 0xF2 is distinguishable from 0xE8, with
// the former meant as a decimal point while the latter is punctuation. Presumably this
// intention was largely inherited when the English games were made, as most of the game's
// script uses 0xE8 exclusively; however, 0xF2 appears in the character table for user
// input, meaning it may appear in user-input names (and, conversely, 0xE8 never should).
// Source: https://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_I
// '.' => 0xf2,
'/' => 0xf3,
',' => 0xf4,
'♀' => 0xf5,
num @ '0'..='9' => 0xf6 - ('0' as u8) + (*num as u8),
' ' => 0x7f,
'@' => 0x50,
_ => return None,
})
}
const fn hextostr(hex: u8) -> Option<char> {
Some(match hex {
cap @ 0x80..=0x99 => (('A' as u8) + (cap - 0x80)) as char,
0x9a => '(',
0x9b => ')',
0x9c => ':',
0x9d => ';',
0x9e => '[',
0x9f => ']',
low @ 0xa0..=0xb9 => (('a' as u8) + (low - 0xa0)) as char,
0xe0 => '\'',
0xe3 => '-',
0xe6 => '?',
0xe7 => '!',
0xe8 => '.',
0xec => '▷',
0xed => '▶',
0xee => '▼',
0xef => '♂',
0xf1 => '×',
0xf2 => '.',
0xf3 => '/',
0xf4 => ',',
0xf5 => '♀',
num @ 0xf6..=0xff => (('0' as u8) + (num - 0xf6)) as char,
0x7f => ' ',
0x50 => '@',
_ => return None,
})
}
#[derive(Debug, Eq, PartialEq)]
pub enum Error {
InvalidCharacter,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Error::InvalidCharacter => write!(f, "Invalid character"),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PKString(String);
impl Deref for PKString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for PKString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}", &self.0))
}
}
impl TryFrom<u8> for PKString {
type Error = Error;
fn try_from(ord: u8) -> Result<PKString, Error> {
let mut buf = String::with_capacity(1);
match hextostr(ord) {
Some(chr) => buf.push(chr.clone()),
None => return Err(Error::InvalidCharacter),
}
Ok(PKString(buf))
}
}
impl TryFrom<&[u8]> for PKString {
type Error = Error;
fn try_from(data: &[u8]) -> Result<PKString, Error> {
let mut buf = String::with_capacity(data.len());
for ord in data {
match hextostr(*ord) {
Some(chr) => buf.push(chr.clone()),
None => return Err(Error::InvalidCharacter),
}
}
Ok(PKString(buf))
}
}
impl TryFrom<Vec<u8>> for PKString {
type Error = Error;
fn try_from(data: Vec<u8>) -> Result<PKString, Error> {
PKString::try_from(data.as_slice())
}
}
impl TryFrom<&str> for PKString {
type Error = Error;
fn try_from(data: &str) -> Result<PKString, Error> {
PKString::try_from(String::from(data))
}
}
impl TryFrom<String> for PKString {
type Error = Error;
fn try_from(data: String) -> Result<PKString, Error> {
for chr in data.chars() {
if strtohex(&chr).is_none() {
return Err(Error::InvalidCharacter);
}
}
Ok(PKString(data))
}
}
impl From<PKString> for Vec<u8> {
fn from(pkstr: PKString) -> Vec<u8> {
let mut vec = Vec::with_capacity(pkstr.len());
for chr in pkstr.0.chars() {
if let Some(ord) = strtohex(&chr) {
vec.push(ord);
} else {
// TODO: Change this.
panic!();
}
}
vec
}
}
impl From<PKString> for String {
fn from(pkstr: PKString) -> String {
pkstr.0
}
}
// impl PKString {
// fn as_slice(&self) -> &[u8] {
// self.0.as_slice()
// }
// }

120
pkstrings/src/tests.rs Normal file
View file

@ -0,0 +1,120 @@
// Copyright (C) 2021 "Maxime “pep” Buquet <pep@bouah.net>"
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
// for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::PKString;
use std::convert::TryFrom;
const GARY_SLICE_U8: &[u8] = &[0x86, 0x80, 0x91, 0x98];
const GARY_STR: &str = "GARY";
#[test]
fn test_try_from_u8() {
match PKString::try_from(0x80) { // 'A'
Ok(pkstr) => assert_eq!(pkstr, PKString::try_from("A").unwrap()),
Err(_) => panic!(),
}
match PKString::try_from(0x79) { // Invalid
Ok(_) => panic!(),
Err(_) => (),
}
}
#[test]
fn test_try_from_slice_u8() {
match PKString::try_from(GARY_SLICE_U8) {
Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_STR).unwrap()),
Err(_) => panic!(),
}
let invalid: &[u8] = &[
0x86,
0x80,
0x79, // Invalid
0x98,
];
match PKString::try_from(invalid) {
Ok(_) => panic!(),
Err(_) => (),
}
}
#[test]
fn test_try_from_vec_u8() {
let gary: Vec<u8> = GARY_SLICE_U8.to_vec();
match PKString::try_from(gary) {
Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_STR).unwrap()),
Err(_) => panic!(),
}
let invalid: Vec<u8> = vec![
0x86,
0x80,
0x79, // Invalid
0x98,
];
match PKString::try_from(invalid) {
Ok(_) => panic!(),
Err(_) => (),
}
}
#[test]
fn test_from_ord() {
assert_eq!(PKString::try_from(GARY_SLICE_U8), Ok(PKString::try_from(GARY_STR).unwrap()));
let party_nicks: &[u8] = &[
0x8a, 0x80, 0x83, 0x80, 0x81, 0x91, 0x80, 0x50, 0x50, 0x50, 0x50, 0x8d, 0x88, 0x83,
0x8e, 0x8a, 0x88, 0x8d, 0x86, 0x50, 0x50, 0x50, 0x81, 0x8b, 0x80, 0x92, 0x93, 0x8e,
0x88, 0x92, 0x84, 0x50, 0x50, 0x8e, 0x83, 0x83, 0x88, 0x92, 0x87, 0x50, 0x50, 0x50,
0x50, 0x50, 0x8f, 0x88, 0x83, 0x86, 0x84, 0x98, 0x50, 0x50, 0x50, 0x50, 0x50, 0x82,
0x87, 0x80, 0x91, 0x8c, 0x80, 0x8d, 0x83, 0x84, 0x91, 0x50,
];
let result = "KADABRA@@@@NIDOKING@@@BLASTOISE@@ODDISH@@@@@PIDGEY@@@@@CHARMANDER@";
assert_eq!(PKString::try_from(party_nicks), Ok(PKString::try_from(result).unwrap()));
}
#[test]
fn test_from_chr() {
let result: Vec<u8> = PKString::try_from(GARY_STR).unwrap().into();
assert_eq!(result, GARY_SLICE_U8);
let nidoranf: Vec<u8> = vec![0x8d, 0x88, 0x83, 0x8e, 0x91, 0x80, 0x8d, 0xef];
let result: Vec<u8> = PKString::try_from("NIDORAN♂").unwrap().into();
assert_eq!(result, nidoranf);
}
#[test]
fn test_into_vec_u8() {
let pkstr: PKString = PKString::try_from(GARY_SLICE_U8).unwrap();
let res: Vec<u8> = pkstr.into();
assert_eq!(res, GARY_SLICE_U8);
}
#[test]
fn test_from_string() {
match PKString::try_from(String::from(GARY_STR)) {
Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_SLICE_U8).unwrap()),
Err(_) => panic!(),
}
}
// #[test]
// fn test_as_slice_u8() {
// let gary: Vec<u8> = vec![0x86, 0x80, 0x91, 0x98];
// let pkstr: PKString = PKString::try_from(gary.clone()).unwrap();
// let res: &[u8] = pkstr.as_slice();
// assert_eq!(res, gary.as_slice());
// }