use std::f64;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::i64;
use std::str::FromStr;
use uuid::UUID;
use crate::{scan_for_float, scan_for_integer, scan_for_string};
#[derive(Clone)]
pub enum Atom {
UUID(UUID),
Integer(i64),
Float(f64),
String(String),
}
impl Hash for Atom {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
&Atom::String(ref s) => s.hash(state),
&Atom::Integer(ref s) => s.hash(state),
&Atom::Float(ref s) => format!("{}", s).hash(state),
&Atom::UUID(ref s) => s.hash(state),
}
}
}
impl PartialEq for Atom {
fn eq(&self, other: &Atom) -> bool {
match (self, other) {
(&Atom::String(ref a), &Atom::String(ref b)) => a == b,
(&Atom::Integer(ref a), &Atom::Integer(ref b)) => a == b,
(&Atom::UUID(ref a), &Atom::UUID(ref b)) => a == b,
(&Atom::Float(ref a), &Atom::Float(ref b)) => a - b < 0.0001,
_ => false,
}
}
}
impl Eq for Atom {}
impl fmt::Debug for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Atom::String(ref s) => {
let s = format!("{:?}", s);
write!(f, "'{}'", &s[1..s.len() - 1])
}
&Atom::Integer(ref s) => write!(f, "={}", s),
&Atom::Float(ref s) => write!(f, "^{}", s),
&Atom::UUID(ref s) => write!(f, ">{}", s.to_string()),
}
}
}
impl Atom {
pub fn is_uuid(&self) -> bool {
match self {
&Atom::UUID(_) => true,
_ => false,
}
}
pub fn is_integer(&self) -> bool {
match self {
&Atom::Integer(_) => true,
_ => false,
}
}
pub fn is_float(&self) -> bool {
match self {
&Atom::Float(_) => true,
_ => false,
}
}
pub fn is_string(&self) -> bool {
match self {
&Atom::String(_) => true,
_ => false,
}
}
pub fn parse<'a>(
input: &'a str, context: Option<(&UUID, &UUID)>,
) -> Option<(Self, &'a str)> {
let input = input.trim_start();
match input.chars().next() {
Some('\'') => Self::parse_string(&input[1..]),
Some('=') => Self::parse_integer(&input[1..]),
Some('^') => Self::parse_float(&input[1..]),
Some('>') => {
UUID::parse(&input[1..], context)
.map(|(uu, cdr)| (Atom::UUID(uu), cdr))
}
_ => None,
}
}
fn parse_integer<'a>(input: &'a str) -> Option<(Self, &'a str)> {
let p = scan_for_integer(input).unwrap_or(input.len());
if p == 0 {
None
} else {
let (car, cdr) = input.split_at(p);
i64::from_str(car).ok().map(|i| (Atom::Integer(i), cdr))
}
}
fn parse_float<'a>(input: &'a str) -> Option<(Self, &'a str)> {
let p = scan_for_float(input).unwrap_or(input.len());
if p == 0 {
None
} else {
let (car, cdr) = input.split_at(p);
f64::from_str(car).ok().map(|i| (Atom::Float(i), cdr))
}
}
fn parse_string<'a>(input: &'a str) -> Option<(Self, &'a str)> {
scan_for_string(input).map(|off| {
let (a, b) = input.split_at(off);
(Atom::String(a.to_string()), &b[1..])
})
}
}
#[test]
fn atom_uuid() {
let atom = Atom::UUID(UUID::Name { name: 0, scope: 0 });
assert_eq!(atom.is_uuid(), true);
assert_eq!(atom.is_integer(), false);
assert_eq!(atom.is_float(), false);
assert_eq!(atom.is_string(), false);
assert_eq!(&format!("{}", atom), ">0");
}
#[test]
fn atom_integer() {
let atom = Atom::Integer(42);
assert_eq!(atom.is_uuid(), false);
assert_eq!(atom.is_integer(), true);
assert_eq!(atom.is_float(), false);
assert_eq!(atom.is_string(), false);
assert_eq!(&format!("{}", atom), "=42");
}
#[test]
fn atom_float() {
let atom = Atom::Float(3.14);
assert_eq!(atom.is_uuid(), false);
assert_eq!(atom.is_integer(), false);
assert_eq!(atom.is_float(), true);
assert_eq!(atom.is_string(), false);
assert_eq!(&format!("{}", atom), "^3.14");
}
#[test]
fn atom_string() {
let atom = Atom::String("atom".to_string());
assert_eq!(atom.is_uuid(), false);
assert_eq!(atom.is_integer(), false);
assert_eq!(atom.is_float(), false);
assert_eq!(atom.is_string(), true);
assert_eq!(&format!("{}", atom), "'atom'");
}