pkstrings: Move PKString and tests in their own modules
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
025828debc
commit
5900325086
3 changed files with 331 additions and 212 deletions
|
@ -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
208
pkstrings/src/pkstring.rs
Normal 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
120
pkstrings/src/tests.rs
Normal 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());
|
||||
// }
|
Loading…
Reference in a new issue