1use core::{
9 convert::TryFrom,
10 fmt::{Display, Write},
11 hash::Hash,
12 ops::Deref,
13};
14
15use crate::{datum_error, DatumError, DatumResult, DatumToken};
16
17#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
24pub enum DatumAtom<B: Deref<Target = str>> {
25 String(B),
26 Symbol(B),
27 Integer(i64),
28 Float(f64),
29 Boolean(bool),
30 Nil,
31}
32
33impl<B: Deref<Target = str>> Default for DatumAtom<B> {
34 fn default() -> Self {
35 Self::Nil
36 }
37}
38
39impl<B: Default + Deref<Target = str>> TryFrom<DatumToken<B>> for DatumAtom<B> {
40 type Error = DatumError;
41
42 fn try_from(token: DatumToken<B>) -> DatumResult<DatumAtom<B>> {
45 match token {
46 DatumToken::String(_, b) => Ok(DatumAtom::String(b)),
47 DatumToken::Symbol(_, b) => Ok(DatumAtom::Symbol(b)),
48 DatumToken::SpecialID(at, b) => {
49 if b.eq_ignore_ascii_case("t") {
50 Ok(DatumAtom::Boolean(true))
51 } else if b.eq_ignore_ascii_case("f") {
52 Ok(DatumAtom::Boolean(false))
53 } else if b.eq_ignore_ascii_case("nil") {
54 Ok(DatumAtom::Nil)
55 } else if b.eq("{}#") {
56 Ok(DatumAtom::Symbol(B::default()))
57 } else if b.eq_ignore_ascii_case("i+nan.0") {
58 Ok(DatumAtom::Float(f64::NAN))
59 } else if b.eq_ignore_ascii_case("i+inf.0") {
60 Ok(DatumAtom::Float(f64::INFINITY))
61 } else if b.eq_ignore_ascii_case("i-inf.0") {
62 Ok(DatumAtom::Float(f64::NEG_INFINITY))
63 } else if b.starts_with('x') || b.starts_with('X') {
64 let res = i64::from_str_radix(&b[1..], 16);
65 if let Ok(v) = res {
66 Ok(DatumAtom::Integer(v))
67 } else {
68 Err(datum_error!(BadData, at, "invalid hex integer"))
69 }
70 } else {
71 Err(datum_error!(BadData, at, "invalid special ID"))
72 }
73 }
74 DatumToken::Integer(_, v) => Ok(DatumAtom::Integer(v)),
75 DatumToken::Float(_, v) => Ok(DatumAtom::Float(v)),
76 _ => Err(datum_error!(
77 BadData,
78 token.offset(),
79 "token not atomizable"
80 )),
81 }
82 }
83}
84
85impl<B: Deref<Target = str>> DatumAtom<B> {
86 pub fn write(&self, f: &mut dyn Write) -> core::fmt::Result {
88 match &self {
89 DatumAtom::String(v) => DatumToken::String(0, v.deref()).write(f),
90 DatumAtom::Symbol(v) => DatumToken::Symbol(0, v.deref()).write(f),
91 DatumAtom::Integer(v) => {
92 let v: DatumToken<&'static str> = DatumToken::Integer(0, *v);
93 v.write(f)
94 }
95 DatumAtom::Float(v) => {
96 let v: DatumToken<&'static str> = DatumToken::Float(0, *v);
97 v.write(f)
98 }
99 DatumAtom::Boolean(true) => f.write_str("#t"),
100 DatumAtom::Boolean(false) => f.write_str("#f"),
101 DatumAtom::Nil => f.write_str("#nil"),
102 }
103 }
104}
105
106impl<B: Deref<Target = str>> Display for DatumAtom<B> {
107 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108 self.write(f)
109 }
110}
111
112impl<B: Deref<Target = str>> Hash for DatumAtom<B> {
113 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
114 match self {
117 Self::String(s) => {
118 state.write_u8(0);
119 s.hash(state)
120 }
121 Self::Symbol(s) => {
122 state.write_u8(1);
123 s.hash(state)
124 }
125 Self::Integer(i) => {
126 state.write_u8(2);
127 i.hash(state)
128 }
129 Self::Float(f) => {
130 state.write_u8(3);
131 f.to_bits().hash(state)
132 }
133 Self::Boolean(b) => {
134 state.write_u8(4);
135 b.hash(state)
136 }
137 Self::Nil => state.write_u8(5),
138 }
139 }
140}
141
142macro_rules! as_x_result {
143 ($caller:ident, $callee:ident, $type:ty) => {
144 #[doc = concat!("Wraps [DatumMayContainAtom::", stringify!($callee), "] to return [Result], using the given error generator.")]
145 fn $caller<E, F: FnOnce() -> E>(&self, err: F) -> Result<$type, E> {
146 match self.$callee() {
147 Some(v) => Ok(v),
148 None => Err(err())
149 }
150 }
151 };
152}
153
154macro_rules! as_x {
155 ($name:ident, $name_result:ident, $variant:ident, $type:ty) => {
156 #[doc = concat!("Attempts to retrieve [DatumAtom::", stringify!($variant), "], else [None].")]
157 fn $name(&self) -> Option<$type> {
158 if let Some(DatumAtom::$variant(res)) = self.as_atom() {
159 Some(res)
160 } else {
161 None
162 }
163 }
164 as_x_result!($name_result, $name, $type);
165 };
166 ($name:ident, $name_result:ident, $variant:ident, $type:ty, $adjust:tt) => {
167 #[doc = concat!("Attempts to retrieve [DatumAtom::", stringify!($variant), "], else [None].")]
168 fn $name(&self) -> Option<$type> {
169 if let Some(DatumAtom::$variant(res)) = self.as_atom() {
170 Some($adjust res)
171 } else {
172 None
173 }
174 }
175 as_x_result!($name_result, $name, $type);
176 };
177}
178
179pub trait DatumMayContainAtom<B: Deref<Target = str>> {
181 fn as_atom(&self) -> Option<&DatumAtom<B>>;
183 as_x_result!(as_atom_result, as_atom, &DatumAtom<B>);
184 as_x!(as_str, as_str_result, String, &B);
185 as_x!(as_sym, as_sym_result, Symbol, &B);
186 as_x!(as_i64, as_i64_result, Integer, i64, *);
187 as_x!(as_f64, as_f64_result, Float, f64, *);
188 fn as_number(&self) -> Option<f64> {
191 if let Some(res) = self.as_atom() {
192 if let DatumAtom::Float(res) = res {
193 Some(*res)
194 } else if let DatumAtom::Integer(res) = res {
195 Some(*res as f64)
196 } else {
197 None
198 }
199 } else {
200 None
201 }
202 }
203 as_x_result!(as_number_result, as_number, f64);
204 as_x!(as_bool, as_bool_result, Boolean, bool, *);
205 fn as_nil(&self) -> Option<()> {
207 if let Some(DatumAtom::Nil) = self.as_atom() {
208 Some(())
209 } else {
210 None
211 }
212 }
213 as_x_result!(as_nil_result, as_nil, ());
214}
215
216impl<B: Deref<Target = str>> DatumMayContainAtom<B> for DatumAtom<B> {
217 fn as_atom(&self) -> Option<&DatumAtom<B>> {
218 Some(self)
219 }
220}