use crate::{ParseError, SymbolCode};
use std::cmp::{Ord, PartialEq, PartialOrd};
use std::convert::From;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Eq, Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Default)]
pub struct Symbol {
value: u64,
}
impl Symbol {
#[inline]
#[must_use]
pub fn raw(&self) -> u64 {
self.value
}
#[inline]
#[must_use]
pub fn code(&self) -> SymbolCode {
SymbolCode::from(self.value >> 8)
}
#[inline]
#[must_use]
pub fn is_valid(&self) -> bool {
self.code().is_valid()
}
#[inline]
#[must_use]
pub fn precision(&self) -> u8 {
self.value as u8
}
#[inline]
#[must_use]
pub fn new() -> Self {
Self { value: 0 }
}
#[inline]
#[must_use]
pub fn from_precision(symcode: SymbolCode, precision: u8) -> Self {
let value = (symcode.raw() << 8) | precision as u64;
Symbol { value }
}
}
impl Display for Symbol {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(format!("{},{}", self.precision(), self.code()).as_str())
}
}
impl FromStr for Symbol {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split(',').collect::<Vec<&str>>();
if parts.len() != 2 {
return Err(ParseError::BadFormat);
}
let precision = match parts[0].parse::<u8>() {
Ok(p) => p,
Err(_) => return Err(ParseError::BadPrecision(parts[0].to_string())),
};
let symcode = match SymbolCode::from_str(parts[1]) {
Ok(sc) => sc,
Err(_) => return Err(ParseError::BadSymbolCode(parts[1].to_string())),
};
Ok(Symbol::from_precision(symcode, precision))
}
}
impl From<&str> for Symbol {
#[inline]
#[must_use]
fn from(str: &str) -> Self {
Self::from_str(str).unwrap_or_else(|e| panic!("failed to parse symbol: {}", e))
}
}
impl From<u64> for Symbol {
#[inline]
#[must_use]
fn from(value: u64) -> Self {
Symbol { value }
}
}
impl From<Symbol> for u64 {
#[inline]
#[must_use]
fn from(sym: Symbol) -> Self {
sym.value
}
}
impl AsRef<Symbol> for Symbol {
#[inline]
#[must_use]
fn as_ref(&self) -> &Symbol {
self
}
}
impl From<Symbol> for bool {
#[inline]
#[must_use]
fn from(sym: Symbol) -> Self {
sym.raw() != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_cdt_1() {
assert_eq!(0, Symbol::new().raw());
}
#[test]
fn test_cdt_2() {
assert_eq!(0, Symbol::from(0).raw());
assert_eq!(1, Symbol::from(1).raw());
assert_eq!(u64::MAX, Symbol::from(u64::MAX).raw());
assert_eq!(0 as u64, Symbol::from(0).into());
}
#[test]
fn test_cdt_3() {
let sc0 = SymbolCode::from("A");
let sc1 = SymbolCode::from("Z");
let sc2 = SymbolCode::from("AAAAAAA");
let sc3 = SymbolCode::from("ZZZZZZZ");
assert_eq!(16640, Symbol::from_precision(sc0, 0).raw());
assert_eq!(23040, Symbol::from_precision(sc1, 0).raw());
assert_eq!(4702111234474983680, Symbol::from_precision(sc2, 0).raw());
assert_eq!(6510615555426900480, Symbol::from_precision(sc3, 0).raw());
assert_eq!(16640, Symbol::from_precision(sc0, 0).raw());
assert_eq!(23040, Symbol::from_precision(sc1, 0).raw());
assert_eq!(4702111234474983680, Symbol::from_precision(sc2, 0).raw());
assert_eq!(6510615555426900480, Symbol::from_precision(sc3, 0).raw());
}
#[test]
fn test_cdt_4() {
assert_eq!(true, Symbol::from(16640).is_valid()); assert_eq!(true, Symbol::from(23040).is_valid()); assert_eq!(true, Symbol::from(4702111234474983680).is_valid()); assert_eq!(true, Symbol::from(6510615555426900480).is_valid());
assert_eq!(false, Symbol::from(16639).is_valid());
assert_eq!(false, Symbol::from(6510615555426900736).is_valid());
}
#[test]
fn test_cdt_5() {
let sc0 = SymbolCode::from("A");
let sc1 = SymbolCode::from("Z");
let sc2 = SymbolCode::from("AAAAAAA");
let sc3 = SymbolCode::from("ZZZZZZZ");
assert_eq!(0, Symbol::from_precision(sc0, 0).precision());
assert_eq!(0, Symbol::from_precision(sc1, 0).precision());
assert_eq!(0, Symbol::from_precision(sc2, 0).precision());
assert_eq!(0, Symbol::from_precision(sc3, 0).precision());
assert_eq!(255, Symbol::from_precision(sc0, 255).precision());
assert_eq!(255, Symbol::from_precision(sc1, 255).precision());
assert_eq!(255, Symbol::from_precision(sc2, 255).precision());
assert_eq!(255, Symbol::from_precision(sc3, 255).precision());
}
#[test]
fn test_cdt_6() {
let sc0 = SymbolCode::from("A");
let sc1 = SymbolCode::from("Z");
let sc2 = SymbolCode::from("AAAAAAA");
let sc3 = SymbolCode::from("ZZZZZZZ");
assert_eq!(sc0, Symbol::from_precision(sc0, 0).code());
assert_eq!(sc1, Symbol::from_precision(sc1, 0).code());
assert_eq!(sc2, Symbol::from_precision(sc2, 0).code());
assert_eq!(sc3, Symbol::from_precision(sc3, 0).code());
}
#[test]
fn test_cdt_7() {
assert_eq!(false, Symbol::from(0).into());
assert_eq!(true, Symbol::from(1).into());
assert_eq!(false, Symbol::from_precision(SymbolCode::from(""), 0).into());
assert_eq!(true, Symbol::from_precision(SymbolCode::from("SYMBOLL"), 0).into());
}
#[test]
fn test_cdt_8() {
let sc0 = SymbolCode::from("A");
let sc1 = SymbolCode::from("Z");
let sc2 = SymbolCode::from("AAAAAAA");
let sc3 = SymbolCode::from("ZZZZZZZ");
assert_eq!(true, Symbol::from_precision(sc0, 0) == Symbol::from_precision(sc0, 0));
assert_eq!(true, Symbol::from_precision(sc1, 0) == Symbol::from_precision(sc1, 0));
assert_eq!(true, Symbol::from_precision(sc2, 0) == Symbol::from_precision(sc2, 0));
assert_eq!(true, Symbol::from_precision(sc3, 0) == Symbol::from_precision(sc3, 0));
assert_eq!(true, Symbol::from_precision(sc0, 0) != Symbol::new());
assert_eq!(true, Symbol::from_precision(sc1, 0) != Symbol::new());
assert_eq!(true, Symbol::from_precision(sc2, 0) != Symbol::new());
assert_eq!(true, Symbol::from_precision(sc3, 0) != Symbol::new());
assert_eq!(true, Symbol::new() < Symbol::from_precision(sc0, 0));
assert_eq!(true, Symbol::new() < Symbol::from_precision(sc1, 0));
assert_eq!(true, Symbol::new() < Symbol::from_precision(sc2, 0));
assert_eq!(true, Symbol::new() < Symbol::from_precision(sc3, 0));
}
#[test]
fn test_from_str() {
assert_eq!(Symbol::from("10,SYM"), Symbol::from_precision(SymbolCode::from("SYM"), 10));
assert_eq!(Symbol::from("0,"), Symbol::from_precision(SymbolCode::from(""), 0));
assert_eq!(Symbol::from("5,SYM").to_string(), "5,SYM");
assert_eq!(Symbol::from("50,SYM").to_string(), "50,SYM"); assert_eq!(Symbol::from("5,SYM").precision(), 5);
assert_eq!(Symbol::from("5,SYM").code(), SymbolCode::from("SYM"));
}
#[test]
#[allow(unused)]
#[should_panic(expected = "failed to parse symbol: bad symbol code: a")]
fn test_from_str_panic_1() {
Symbol::from("10,a");
}
#[test]
#[allow(unused)]
#[should_panic(expected = "failed to parse symbol: bad precision: 1000")]
fn test_from_str_panic_2() {
Symbol::from("1000,SYM");
}
#[test]
#[allow(unused)]
#[should_panic(expected = "failed to parse symbol: bad format")]
fn test_from_str_panic_3() {
Symbol::from("10SYM");
}
#[test]
#[allow(unused)]
#[should_panic(expected = "bad symbol code: SYM,10")]
fn test_from_str_panic_4() {
SymbolCode::from("SYM,10");
}
#[test]
fn test_from_self() {
let sym = Symbol::from("4,ABCDEFG");
assert_eq!(Symbol::from(sym), sym);
}
#[test]
fn test_from_str_failed() {
assert_eq!(
"4,ABCDEFGH".parse::<Symbol>(),
Err(ParseError::BadSymbolCode("ABCDEFGH".to_string()))
);
assert_eq!("".parse::<Symbol>(), Err(ParseError::BadFormat));
assert_eq!("A,B".parse::<Symbol>(), Err(ParseError::BadPrecision("A".to_string())));
}
proptest! {
#[test]
fn random_symbols(precision in 0..100, symcode in "[[A-Z]]{1,7}") {
let sym_str = format!("{},{}", precision, symcode);
let sym = Symbol::from(sym_str.as_str());
prop_assert_eq!(sym.to_string(), sym_str);
}
}
}