tau_engine/value.rs
1use std::borrow::Cow;
2use std::collections::{HashMap, HashSet};
3use std::hash::BuildHasher;
4
5/// A dynamic data type that the solver can reason on.
6#[derive(Clone)]
7pub enum Value<'a> {
8 /// Represents an empty type.
9 Null,
10 /// Represents a boolean.
11 Bool(bool),
12 /// Represents a float.
13 Float(f64),
14 /// Represents an integer.
15 Int(i64),
16 /// Represents an unsigned integer.
17 UInt(u64),
18 /// Represents a string.
19 String(Cow<'a, str>),
20 /// Represents an array.
21 Array(&'a dyn Array),
22 /// Represents an object.
23 Object(&'a dyn Object),
24}
25
26impl<'a> Value<'a> {
27 /// Returns true if the `Value` is an Array.
28 #[inline]
29 pub fn is_array(&self) -> bool {
30 matches!(self, Self::Array(_))
31 }
32
33 /// Returns true if the `Value` is a Bool.
34 #[inline]
35 pub fn is_bool(&self) -> bool {
36 matches!(self, Self::Bool(_))
37 }
38
39 /// Returns true if the `Value` is a Float.
40 #[inline]
41 pub fn is_f64(&self) -> bool {
42 matches!(self, Self::Float(_))
43 }
44
45 /// Returns true if the `Value` is an Int.
46 #[inline]
47 pub fn is_i64(&self) -> bool {
48 matches!(self, Self::Int(_))
49 }
50
51 /// Returns true if the `Value` is a Null.
52 #[inline]
53 pub fn is_null(&self) -> bool {
54 matches!(self, Self::Null)
55 }
56
57 /// Returns true if the `Value` is an Object.
58 #[inline]
59 pub fn is_object(&self) -> bool {
60 matches!(self, Self::Object(_))
61 }
62
63 /// Returns true if the `Value` is a String.
64 #[inline]
65 pub fn is_string(&self) -> bool {
66 matches!(self, Self::String(_))
67 }
68
69 /// Returns true if the `Value` is a UInt.
70 #[inline]
71 pub fn is_u64(&self) -> bool {
72 matches!(self, Self::UInt(_))
73 }
74
75 /// Return the associated array if the `Value` is an Array.
76 #[inline]
77 pub fn as_array(&self) -> Option<&dyn Array> {
78 match self {
79 Self::Array(a) => Some(*a),
80 _ => None,
81 }
82 }
83
84 /// Return the associated boolean if the `Value` is a Bool.
85 #[inline]
86 pub fn as_bool(&self) -> Option<bool> {
87 match self {
88 Self::Bool(b) => Some(*b),
89 _ => None,
90 }
91 }
92
93 /// Return the associated f64 if the `Value` is a Float.
94 #[inline]
95 pub fn as_f64(&self) -> Option<f64> {
96 match self {
97 Self::Float(n) => Some(*n),
98 _ => None,
99 }
100 }
101
102 /// Return the associated i64 if the `Value` is a Int.
103 #[inline]
104 pub fn as_i64(&self) -> Option<i64> {
105 match self {
106 Self::Int(n) => Some(*n),
107 _ => None,
108 }
109 }
110
111 /// Return the associated () if the `Value` is a Null.
112 #[inline]
113 pub fn as_null(&self) -> Option<()> {
114 match self {
115 Self::Null => Some(()),
116 _ => None,
117 }
118 }
119
120 /// Return the associated object if the `Value` is an Object.
121 #[inline]
122 pub fn as_object(&self) -> Option<&dyn Object> {
123 match self {
124 Self::Object(o) => Some(*o),
125 _ => None,
126 }
127 }
128
129 /// Return the associated str if the `Value` is a String.
130 #[inline]
131 pub fn as_str(&self) -> Option<&str> {
132 match self {
133 Self::String(s) => Some(s),
134 _ => None,
135 }
136 }
137
138 /// Return the associated u64 if the `Value` is a UInt.
139 #[inline]
140 pub fn as_u64(&self) -> Option<u64> {
141 match self {
142 Self::UInt(n) => Some(*n),
143 _ => None,
144 }
145 }
146
147 /// Returns the `Value` as an i64 if possible.
148 ///
149 /// Currently supports: Int & UInt.
150 #[inline]
151 pub fn to_i64(&self) -> Option<i64> {
152 match self {
153 Self::Int(n) => Some(*n),
154 Self::UInt(n) => {
155 if *n <= i64::MAX as u64 {
156 Some(*n as i64)
157 } else {
158 None
159 }
160 }
161 _ => None,
162 }
163 }
164
165 /// Returns the `Value` as a String if possible.
166 ///
167 /// Currently supports: Bool, Float, Int, String & UInt.
168 #[inline]
169 pub fn to_string(&self) -> Option<String> {
170 match self {
171 Self::Bool(b) => Some(b.to_string()),
172 Self::Int(i) => Some(i.to_string()),
173 Self::UInt(u) => Some(u.to_string()),
174 Self::Float(f) => Some(f.to_string()),
175 Self::String(s) => Some(s.to_string()),
176 _ => None,
177 }
178 }
179}
180
181/// A **data type** that can be represented as a `Value`.
182///
183/// # Implementations
184///
185/// As long as the **data type** can be coerced into one of the values provided by `Value` then
186/// `AsValue` can be implemented on that type. Below is a contrived example:
187///
188/// ```
189/// use std::borrow::Cow;
190///
191/// use tau_engine::{AsValue, Value};
192///
193/// enum Foo {
194/// Bar,
195/// Baz
196/// }
197///
198/// impl AsValue for Foo {
199/// fn as_value(&self) -> Value<'_> {
200/// match self {
201/// Self::Bar => Value::String(Cow::Borrowed("bar")),
202/// Self::Baz => Value::String(Cow::Borrowed("baz")),
203/// }
204/// }
205/// }
206/// ```
207#[cfg(not(feature = "sync"))]
208pub trait AsValue {
209 /// Returns the implemented type as a `Value`
210 ///
211 /// # Example
212 ///
213 /// ```
214 /// use tau_engine::AsValue;
215 ///
216 /// let value = "foobar".as_value();
217 /// ```
218 fn as_value(&self) -> Value<'_>;
219}
220#[cfg(feature = "sync")]
221pub trait AsValue: Send + Sync {
222 fn as_value(&self) -> Value<'_>;
223}
224
225impl AsValue for () {
226 #[inline]
227 fn as_value(&self) -> Value<'_> {
228 Value::Null
229 }
230}
231
232impl AsValue for bool {
233 #[inline]
234 fn as_value(&self) -> Value<'_> {
235 Value::Bool(*self)
236 }
237}
238
239impl AsValue for str {
240 #[inline]
241 fn as_value(&self) -> Value<'_> {
242 Value::String(Cow::Borrowed(self))
243 }
244}
245
246impl AsValue for String {
247 #[inline]
248 fn as_value(&self) -> Value<'_> {
249 Value::String(Cow::Borrowed(self))
250 }
251}
252
253impl<V> AsValue for HashSet<V>
254where
255 V: AsValue,
256{
257 #[inline]
258 fn as_value(&self) -> Value<'_> {
259 Value::Array(self)
260 }
261}
262
263impl<V> AsValue for Option<V>
264where
265 V: AsValue,
266{
267 #[inline]
268 fn as_value(&self) -> Value<'_> {
269 self.as_ref().map(|v| v.as_value()).unwrap_or(Value::Null)
270 }
271}
272
273impl<V> AsValue for Vec<V>
274where
275 V: AsValue,
276{
277 #[inline]
278 fn as_value(&self) -> Value<'_> {
279 Value::Array(self)
280 }
281}
282
283macro_rules! impl_as_value_float {
284 ($ty:ty) => {
285 impl AsValue for $ty {
286 #[inline]
287 fn as_value(&self) -> Value<'_> {
288 Value::Float(*self as f64)
289 }
290 }
291 };
292}
293
294impl_as_value_float!(f32);
295impl_as_value_float!(f64);
296
297macro_rules! impl_as_value_int {
298 ($ty:ty) => {
299 impl AsValue for $ty {
300 #[inline]
301 fn as_value(&self) -> Value<'_> {
302 Value::Int(*self as i64)
303 }
304 }
305 };
306}
307
308impl_as_value_int!(i8);
309impl_as_value_int!(i16);
310impl_as_value_int!(i32);
311impl_as_value_int!(i64);
312impl_as_value_int!(isize);
313
314macro_rules! impl_as_value_uint {
315 ($ty:ty) => {
316 impl AsValue for $ty {
317 #[inline]
318 fn as_value(&self) -> Value<'_> {
319 Value::UInt(*self as u64)
320 }
321 }
322 };
323}
324
325impl_as_value_uint!(u8);
326impl_as_value_uint!(u16);
327impl_as_value_uint!(u32);
328impl_as_value_uint!(u64);
329impl_as_value_uint!(usize);
330
331/// A **data type** that can be represented as an `Array`.
332///
333/// This allows more complex array-like data types to be represented in a generic way for use as a
334/// `Value`.
335///
336/// # Implementations
337///
338/// As long as the **data type** is considered array-like then `Array` can be implemented on that
339/// type. Below is a contrived example:
340///
341/// ```
342/// use tau_engine::{Array, Value};
343///
344/// // NOTE: Implements Iterator
345/// #[derive(Clone)]
346/// struct Counter {
347/// count: usize,
348/// }
349/// # impl Iterator for Counter {
350/// # // we will be counting with usize
351/// # type Item = usize;
352///
353/// # // next() is the only required method
354/// # fn next(&mut self) -> Option<Self::Item> {
355/// # // Increment our count. This is why we started at zero.
356/// # self.count += 1;
357///
358/// # // Check to see if we've finished counting or not.
359/// # if self.count < 6 {
360/// # Some(self.count)
361/// # } else {
362/// # None
363/// # }
364/// # }
365/// # }
366/// impl Array for Counter {
367/// fn iter(&self) -> Box<dyn Iterator<Item = Value<'_>> + '_> {
368/// Box::new(self.clone().map(|v| Value::UInt(v as u64)))
369/// }
370///
371/// fn len(&self) -> usize {
372/// self.clone().count()
373/// }
374/// }
375/// ```
376#[allow(clippy::len_without_is_empty)]
377#[cfg(not(feature = "sync"))]
378pub trait Array {
379 /// Returns a boxed iterator of `Value` items.
380 ///
381 /// # Example
382 ///
383 /// ```
384 /// use std::collections::HashSet;
385 /// use tau_engine::{Array, Value};
386 ///
387 /// let mut set = HashSet::new();
388 /// set.insert(1);
389 ///
390 /// let mut value = Array::iter(&set);
391 ///
392 /// assert_eq!(value.next().is_some(), true);
393 /// ```
394 fn iter(&self) -> Box<dyn Iterator<Item = Value<'_>> + '_>;
395
396 /// Returns the length of the array.
397 ///
398 /// # Example
399 ///
400 ///```
401 /// use std::collections::HashSet;
402 /// use tau_engine::{Array, Value};
403 ///
404 /// let mut set = HashSet::new();
405 /// set.insert(1);
406 ///
407 /// let len = Array::len(&set);
408 ///
409 /// assert_eq!(len, 1);
410 /// ```
411 fn len(&self) -> usize;
412}
413#[cfg(feature = "sync")]
414pub trait Array: Send + Sync {
415 fn iter(&self) -> Box<dyn Iterator<Item = Value<'_>> + '_>;
416 fn len(&self) -> usize;
417}
418
419impl<V> Array for HashSet<V>
420where
421 V: AsValue,
422{
423 #[inline]
424 fn iter(&self) -> Box<dyn Iterator<Item = Value<'_>> + '_> {
425 Box::new(self.iter().map(|v| v.as_value()))
426 }
427
428 #[inline]
429 fn len(&self) -> usize {
430 self.len()
431 }
432}
433
434impl<V> Array for Vec<V>
435where
436 V: AsValue,
437{
438 #[inline]
439 fn iter(&self) -> Box<dyn Iterator<Item = Value<'_>> + '_> {
440 Box::new(self.as_slice().iter().map(|v| v.as_value()))
441 }
442
443 #[inline]
444 fn len(&self) -> usize {
445 self.len()
446 }
447}
448
449/// A **data type** that can be represented as an `Object`.
450///
451/// This allows more complex object-like data types to be represented in a generic way for use as a
452/// `Value`.
453///
454/// # Implementations
455///
456/// As long as the **data type** is considered object-like then `Object` can be implemented on that
457/// type. Below is a contrived example:
458///j
459/// ```
460/// use std::borrow::Cow;
461///
462/// use tau_engine::{Object, Value};
463///
464/// struct Foo {
465/// pub bar: String,
466/// pub baz: String,
467/// }
468///
469/// impl Object for Foo {
470/// fn get(&self, key: &str) -> Option<Value<'_>> {
471/// match key {
472/// "bar" => Some(Value::String(Cow::Borrowed(&self.bar))),
473/// "baz" => Some(Value::String(Cow::Borrowed(&self.baz))),
474/// _ => None,
475/// }
476/// }
477///
478/// fn keys(&self) -> Vec<Cow<'_, str>> {
479/// ["bar", "baz"].iter().map(|s| Cow::Borrowed(*s)).collect()
480/// }
481///
482/// fn len(&self) -> usize {
483/// 2
484/// }
485/// }
486/// ```
487///
488/// # Find
489///
490/// The `find` function allows for nested access from an `Object`. A default implementation is
491/// provided by the trait which assumes the key will split on the `.` character. This can be overriden if
492/// required. Below is an example of how find works for a complex data structure.
493///
494/// ```
495/// # use std::borrow::Cow;
496/// # use tau_engine::Value;
497/// use tau_engine::Object;
498///
499/// struct Foo {
500/// pub bar: String,
501/// }
502/// # impl Object for Foo {
503/// # fn get(&self, key: &str) -> Option<Value<'_>> {
504/// # match key {
505/// # "bar" => Some(Value::String(Cow::Borrowed(&self.bar))),
506/// # _ => None,
507/// # }
508/// # }
509/// #
510/// # fn keys(&self) -> Vec<Cow<'_, str>> {
511/// # ["bar"].iter().map(|s| Cow::Borrowed(*s)).collect()
512/// # }
513/// #
514/// # fn len(&self) -> usize {
515/// # 1
516/// # }
517/// # }
518/// struct Baz {
519/// pub foo: Foo,
520/// }
521/// # impl Object for Baz {
522/// # fn get(&self, key: &str) -> Option<Value<'_>> {
523/// # match key {
524/// # "foo" => Some(Value::Object(&self.foo)),
525/// # _ => None,
526/// # }
527/// # }
528/// #
529/// # fn keys(&self) -> Vec<Cow<'_, str>> {
530/// # ["foo"].iter().map(|s| Cow::Borrowed(*s)).collect()
531/// # }
532/// #
533/// # fn len(&self) -> usize {
534/// # 1
535/// # }
536/// # }
537/// let complex = Baz {
538/// foo: Foo {
539/// bar: "foobar".to_owned(),
540/// }
541/// };
542///
543/// let value = complex.find("foo.bar").unwrap();
544///
545/// assert_eq!(value.as_str(), Some("foobar"));
546/// ```
547#[allow(clippy::len_without_is_empty)]
548#[cfg(not(feature = "sync"))]
549pub trait Object {
550 /// Looks for a `Value` by key and returns it if found. The provided implementation will split
551 /// the key on `.` to handle nesting.
552 fn find(&self, key: &str) -> Option<Value<'_>> {
553 let mut v: Option<Value<'_>> = None;
554 for k in key.split('.') {
555 if k.ends_with(']') && k.contains('[') {
556 let mut parts = k.split('[');
557 let k = parts.next().expect("missing key");
558 let i: usize = match parts
559 .next()
560 .and_then(|i| i.strip_suffix("]"))
561 .and_then(|i| i.parse::<usize>().ok())
562 {
563 Some(i) => i,
564 None => return None,
565 };
566 match v {
567 Some(Value::Object(value)) => match value.get(k) {
568 Some(Value::Array(a)) => v = a.iter().nth(i),
569 _ => return None,
570 },
571 Some(_) => return None,
572 None => match <Self as Object>::get(self, k) {
573 Some(Value::Array(a)) => v = a.iter().nth(i),
574 _ => return None,
575 },
576 }
577 } else {
578 match v {
579 Some(Value::Object(value)) => v = value.get(k),
580 Some(_) => return None,
581 None => match <Self as Object>::get(self, k) {
582 Some(value) => v = Some(value),
583 None => return None,
584 },
585 }
586 }
587 }
588 v
589 }
590
591 /// Get the `Value` corresponding to the key.
592 fn get(&self, key: &str) -> Option<Value<'_>>;
593
594 /// Returns the keys for the object.
595 fn keys(&self) -> Vec<Cow<'_, str>>;
596
597 /// Returns the number of elements in the object.
598 fn len(&self) -> usize;
599}
600#[cfg(feature = "sync")]
601pub trait Object: Send + Sync {
602 fn find(&self, key: &str) -> Option<Value<'_>> {
603 let mut v: Option<Value<'_>> = None;
604 for k in key.split('.') {
605 if k.ends_with(']') && k.contains('[') {
606 let mut parts = k.split('[');
607 let k = parts.next().expect("missing key");
608 let i: usize = match parts
609 .next()
610 .and_then(|i| i.strip_suffix("]"))
611 .and_then(|i| i.parse::<usize>().ok())
612 {
613 Some(i) => i,
614 None => return None,
615 };
616 match v {
617 Some(Value::Object(value)) => match value.get(k) {
618 Some(Value::Array(a)) => v = a.iter().nth(i),
619 _ => return None,
620 },
621 Some(_) => return None,
622 None => match <Self as Object>::get(self, k) {
623 Some(Value::Array(a)) => v = a.iter().nth(i),
624 _ => return None,
625 },
626 }
627 } else {
628 match v {
629 Some(Value::Object(value)) => v = value.get(k),
630 Some(_) => return None,
631 None => match <Self as Object>::get(self, k) {
632 Some(value) => v = Some(value),
633 None => return None,
634 },
635 }
636 }
637 }
638 v
639 }
640 fn get(&self, key: &str) -> Option<Value<'_>>;
641 fn keys(&self) -> Vec<Cow<'_, str>>;
642 fn len(&self) -> usize;
643}
644
645#[cfg(not(feature = "sync"))]
646impl<V, S> Object for HashMap<String, V, S>
647where
648 V: AsValue,
649 S: BuildHasher,
650{
651 #[inline]
652 fn get(&self, key: &str) -> Option<Value<'_>> {
653 self.get(key).map(|v| v.as_value())
654 }
655
656 #[inline]
657 fn keys(&self) -> Vec<Cow<'_, str>> {
658 self.keys().map(|s| Cow::Borrowed(s.as_str())).collect()
659 }
660
661 #[inline]
662 fn len(&self) -> usize {
663 self.len()
664 }
665}
666#[cfg(feature = "sync")]
667impl<V, S> Object for HashMap<String, V, S>
668where
669 V: AsValue,
670 S: BuildHasher + Send + Sync,
671{
672 #[inline]
673 fn get(&self, key: &str) -> Option<Value<'_>> {
674 self.get(key).map(|v| v.as_value())
675 }
676
677 #[inline]
678 fn keys(&self) -> Vec<Cow<'_, str>> {
679 self.keys().map(|s| Cow::Borrowed(s.as_str())).collect()
680 }
681
682 #[inline]
683 fn len(&self) -> usize {
684 self.len()
685 }
686}
687
688impl<O: Object> AsValue for O {
689 #[inline]
690 fn as_value(&self) -> Value<'_> {
691 Value::Object(self)
692 }
693}