ron_crdt/
atom.rs

1//! Op payloads
2
3use std::f64;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::i64;
7use std::str::FromStr;
8
9use uuid::UUID;
10
11use crate::{scan_for_float, scan_for_integer, scan_for_string};
12
13/// An Atom in RON is an immutable value of one of the types: UUID,
14/// integer, string and float.
15#[derive(Clone)]
16pub enum Atom {
17    /// References another object
18    UUID(UUID),
19    /// Signed integer. RON specifies arbitrary precision integer, we only support up to 64 bits.
20    Integer(i64),
21    /// IEEE 754 Floating point number.
22    Float(f64),
23    /// UTF-8 String
24    String(String),
25}
26
27impl Hash for Atom {
28    fn hash<H: Hasher>(&self, state: &mut H) {
29        match self {
30            &Atom::String(ref s) => s.hash(state),
31            &Atom::Integer(ref s) => s.hash(state),
32            &Atom::Float(ref s) => format!("{}", s).hash(state),
33            &Atom::UUID(ref s) => s.hash(state),
34        }
35    }
36}
37
38impl PartialEq for Atom {
39    fn eq(&self, other: &Atom) -> bool {
40        match (self, other) {
41            (&Atom::String(ref a), &Atom::String(ref b)) => a == b,
42            (&Atom::Integer(ref a), &Atom::Integer(ref b)) => a == b,
43            (&Atom::UUID(ref a), &Atom::UUID(ref b)) => a == b,
44            (&Atom::Float(ref a), &Atom::Float(ref b)) => a - b < 0.0001,
45            _ => false,
46        }
47    }
48}
49
50impl Eq for Atom {}
51
52impl fmt::Debug for Atom {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        write!(f, "{}", self)
55    }
56}
57
58impl fmt::Display for Atom {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        match self {
61            &Atom::String(ref s) => {
62                let s = format!("{:?}", s);
63                write!(f, "'{}'", &s[1..s.len() - 1])
64            }
65            &Atom::Integer(ref s) => write!(f, "={}", s),
66            &Atom::Float(ref s) => write!(f, "^{}", s),
67            &Atom::UUID(ref s) => write!(f, ">{}", s.to_string()),
68        }
69    }
70}
71
72impl Atom {
73    /// Return true if and only if this is an UUID atom.
74    pub fn is_uuid(&self) -> bool {
75        match self {
76            &Atom::UUID(_) => true,
77            _ => false,
78        }
79    }
80
81    /// Return true if and only if this is an Integer atom.
82    pub fn is_integer(&self) -> bool {
83        match self {
84            &Atom::Integer(_) => true,
85            _ => false,
86        }
87    }
88
89    /// Return true if and only if this is a Float atom.
90    pub fn is_float(&self) -> bool {
91        match self {
92            &Atom::Float(_) => true,
93            _ => false,
94        }
95    }
96
97    /// Return true if and only if this is a String atom.
98    pub fn is_string(&self) -> bool {
99        match self {
100            &Atom::String(_) => true,
101            _ => false,
102        }
103    }
104
105    /// Parse a single atom, returning the Atom and the remaining string. If `context` is not
106    /// `None` UUID Atoms may be compressed against (`previous column UUID` / `previous row UUID`).
107    pub fn parse<'a>(
108        input: &'a str, context: Option<(&UUID, &UUID)>,
109    ) -> Option<(Self, &'a str)> {
110        let input = input.trim_start();
111        match input.chars().next() {
112            Some('\'') => Self::parse_string(&input[1..]),
113            Some('=') => Self::parse_integer(&input[1..]),
114            Some('^') => Self::parse_float(&input[1..]),
115            Some('>') => {
116                UUID::parse(&input[1..], context)
117                    .map(|(uu, cdr)| (Atom::UUID(uu), cdr))
118            }
119            _ => None,
120        }
121    }
122
123    fn parse_integer<'a>(input: &'a str) -> Option<(Self, &'a str)> {
124        let p = scan_for_integer(input).unwrap_or(input.len());
125        if p == 0 {
126            None
127        } else {
128            let (car, cdr) = input.split_at(p);
129            i64::from_str(car).ok().map(|i| (Atom::Integer(i), cdr))
130        }
131    }
132
133    fn parse_float<'a>(input: &'a str) -> Option<(Self, &'a str)> {
134        let p = scan_for_float(input).unwrap_or(input.len());
135        if p == 0 {
136            None
137        } else {
138            let (car, cdr) = input.split_at(p);
139            f64::from_str(car).ok().map(|i| (Atom::Float(i), cdr))
140        }
141    }
142
143    fn parse_string<'a>(input: &'a str) -> Option<(Self, &'a str)> {
144        scan_for_string(input).map(|off| {
145            let (a, b) = input.split_at(off);
146            (Atom::String(a.to_string()), &b[1..])
147        })
148    }
149}
150
151#[test]
152fn atom_uuid() {
153    let atom = Atom::UUID(UUID::Name { name: 0, scope: 0 });
154    assert_eq!(atom.is_uuid(), true);
155    assert_eq!(atom.is_integer(), false);
156    assert_eq!(atom.is_float(), false);
157    assert_eq!(atom.is_string(), false);
158    assert_eq!(&format!("{}", atom), ">0");
159}
160
161#[test]
162fn atom_integer() {
163    let atom = Atom::Integer(42);
164    assert_eq!(atom.is_uuid(), false);
165    assert_eq!(atom.is_integer(), true);
166    assert_eq!(atom.is_float(), false);
167    assert_eq!(atom.is_string(), false);
168    assert_eq!(&format!("{}", atom), "=42");
169}
170
171#[test]
172fn atom_float() {
173    let atom = Atom::Float(3.14);
174    assert_eq!(atom.is_uuid(), false);
175    assert_eq!(atom.is_integer(), false);
176    assert_eq!(atom.is_float(), true);
177    assert_eq!(atom.is_string(), false);
178    assert_eq!(&format!("{}", atom), "^3.14");
179}
180
181#[test]
182fn atom_string() {
183    let atom = Atom::String("atom".to_string());
184    assert_eq!(atom.is_uuid(), false);
185    assert_eq!(atom.is_integer(), false);
186    assert_eq!(atom.is_float(), false);
187    assert_eq!(atom.is_string(), true);
188    assert_eq!(&format!("{}", atom), "'atom'");
189}