1use std::cell::RefCell;
24use std::rc::Rc;
25
26use crate::ast::stmt::BinaryOpKind;
27use crate::interpreter::RuntimeValue;
28use crate::semantics::{arith, collections, compare};
29
30pub struct RuntimeRef<'a>(RuntimeRefInner<'a>);
42
43enum RuntimeRefInner<'a> {
44 Borrowed(&'a RuntimeValue),
45 #[cfg(feature = "narrow-value")]
46 Owned(RuntimeValue),
47}
48
49impl std::ops::Deref for RuntimeRef<'_> {
50 type Target = RuntimeValue;
51 #[inline]
52 fn deref(&self) -> &RuntimeValue {
53 match &self.0 {
54 RuntimeRefInner::Borrowed(r) => r,
55 #[cfg(feature = "narrow-value")]
56 RuntimeRefInner::Owned(v) => v,
57 }
58 }
59}
60
61impl AsRef<RuntimeValue> for RuntimeRef<'_> {
62 #[inline]
63 fn as_ref(&self) -> &RuntimeValue {
64 self
65 }
66}
67
68impl std::fmt::Debug for RuntimeRef<'_> {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 (**self).fmt(f)
71 }
72}
73
74#[cfg(not(feature = "narrow-value"))]
79mod repr {
80 use super::*;
81
82 #[derive(Clone, Debug)]
84 pub struct Value(pub(super) RuntimeValue);
85
86 impl Value {
87 #[inline]
88 pub fn int(n: i64) -> Self {
89 Value(RuntimeValue::Int(n))
90 }
91 #[inline]
92 pub fn float(f: f64) -> Self {
93 Value(RuntimeValue::Float(f))
94 }
95 #[inline]
96 pub fn bool(b: bool) -> Self {
97 Value(RuntimeValue::Bool(b))
98 }
99 #[inline]
100 pub fn nothing() -> Self {
101 Value(RuntimeValue::Nothing)
102 }
103
104 #[inline]
105 pub fn from_runtime(rv: RuntimeValue) -> Self {
106 Value(rv)
107 }
108 #[inline]
109 pub fn into_runtime(self) -> RuntimeValue {
110 self.0
111 }
112
113 #[inline]
116 pub fn as_runtime(&self) -> RuntimeRef<'_> {
117 RuntimeRef(RuntimeRefInner::Borrowed(&self.0))
118 }
119
120 #[inline]
126 pub fn as_runtime_ref(&self) -> Option<&RuntimeValue> {
127 Some(&self.0)
128 }
129
130 #[inline]
132 pub fn as_runtime_mut(&mut self) -> &mut RuntimeValue {
133 &mut self.0
134 }
135
136 #[inline]
137 pub fn as_int(&self) -> Option<i64> {
138 match &self.0 {
139 RuntimeValue::Int(n) => Some(*n),
140 _ => None,
141 }
142 }
143 #[inline]
144 pub fn as_bool(&self) -> Option<bool> {
145 match &self.0 {
146 RuntimeValue::Bool(b) => Some(*b),
147 _ => None,
148 }
149 }
150 #[inline]
151 pub fn as_float(&self) -> Option<f64> {
152 match &self.0 {
153 RuntimeValue::Float(f) => Some(*f),
154 _ => None,
155 }
156 }
157 #[inline]
158 pub fn is_int(&self) -> bool {
159 matches!(self.0, RuntimeValue::Int(_))
160 }
161
162 #[inline]
170 pub fn add(&self, rhs: &Value) -> Result<Value, String> {
171 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
172 if let Some(s) = a.checked_add(*b) {
173 return Ok(Value::int(s));
174 }
175 }
176 arith::add(self.0.clone(), rhs.0.clone()).map(Value)
177 }
178 #[inline]
179 pub fn sub(&self, rhs: &Value) -> Result<Value, String> {
180 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
181 if let Some(s) = a.checked_sub(*b) {
182 return Ok(Value::int(s));
183 }
184 }
185 arith::subtract(self.0.clone(), rhs.0.clone()).map(Value)
186 }
187 #[inline]
188 pub fn mul(&self, rhs: &Value) -> Result<Value, String> {
189 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
190 if let Some(p) = a.checked_mul(*b) {
191 return Ok(Value::int(p));
192 }
193 }
194 arith::multiply(self.0.clone(), rhs.0.clone()).map(Value)
195 }
196
197 #[inline]
200 pub fn lt(&self, rhs: &Value) -> Result<Value, String> {
201 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
202 return Ok(Value::bool(a < b));
203 }
204 compare::compare(BinaryOpKind::Lt, &self.0, &rhs.0).map(Value)
205 }
206 #[inline]
207 pub fn gt(&self, rhs: &Value) -> Result<Value, String> {
208 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
209 return Ok(Value::bool(a > b));
210 }
211 compare::compare(BinaryOpKind::Gt, &self.0, &rhs.0).map(Value)
212 }
213 #[inline]
214 pub fn lte(&self, rhs: &Value) -> Result<Value, String> {
215 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
216 return Ok(Value::bool(a <= b));
217 }
218 compare::compare(BinaryOpKind::LtEq, &self.0, &rhs.0).map(Value)
219 }
220 #[inline]
221 pub fn gte(&self, rhs: &Value) -> Result<Value, String> {
222 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
223 return Ok(Value::bool(a >= b));
224 }
225 compare::compare(BinaryOpKind::GtEq, &self.0, &rhs.0).map(Value)
226 }
227 #[inline]
228 pub fn eq_op(&self, rhs: &Value) -> Value {
229 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
230 return Value::bool(a == b);
231 }
232 Value::bool(self.values_equal(rhs))
233 }
234 #[inline]
235 pub fn neq_op(&self, rhs: &Value) -> Value {
236 if let (RuntimeValue::Int(a), RuntimeValue::Int(b)) = (&self.0, &rhs.0) {
237 return Value::bool(a != b);
238 }
239 Value::bool(!self.values_equal(rhs))
240 }
241 }
242}
243
244#[cfg(feature = "narrow-value")]
249mod repr {
250 use super::*;
251 use crate::vm::nanbox::Narrow;
252
253 #[derive(Clone, Debug)]
255 pub struct Value(pub(super) Narrow);
256
257 impl Value {
258 #[inline]
259 pub fn int(n: i64) -> Self {
260 Value(Narrow::int(n))
261 }
262 #[inline]
263 pub fn float(f: f64) -> Self {
264 Value(Narrow::float(f))
265 }
266 #[inline]
267 pub fn bool(b: bool) -> Self {
268 Value(Narrow::bool(b))
269 }
270 #[inline]
271 pub fn nothing() -> Self {
272 Value(Narrow::nothing())
273 }
274
275 #[inline]
276 pub fn from_runtime(rv: RuntimeValue) -> Self {
277 Value(Narrow::from_runtime(rv))
278 }
279 #[inline]
280 pub fn into_runtime(self) -> RuntimeValue {
281 self.0.into_runtime()
282 }
283
284 #[inline]
288 pub fn as_runtime(&self) -> RuntimeRef<'_> {
289 match self.0.as_heap() {
290 Some(rv) => RuntimeRef(RuntimeRefInner::Borrowed(rv)),
291 None => RuntimeRef(RuntimeRefInner::Owned(self.0.to_runtime())),
292 }
293 }
294
295 #[inline]
303 pub fn as_runtime_ref(&self) -> Option<&RuntimeValue> {
304 self.0.as_heap()
305 }
306
307 #[inline]
312 pub fn as_runtime_mut(&mut self) -> &mut RuntimeValue {
313 self.0.make_heap_mut()
314 }
315
316 #[inline]
317 pub fn as_int(&self) -> Option<i64> {
318 self.0.as_int()
319 }
320 #[inline]
321 pub fn as_bool(&self) -> Option<bool> {
322 self.0.as_bool()
323 }
324 #[inline]
325 pub fn as_float(&self) -> Option<f64> {
326 self.0.as_float()
327 }
328 #[inline]
329 pub fn is_int(&self) -> bool {
330 self.0.as_int().is_some()
331 }
332
333 #[inline]
340 pub fn add(&self, rhs: &Value) -> Result<Value, String> {
341 if let Some(n) = self.0.int_add(&rhs.0) {
342 return Ok(Value(n));
343 }
344 arith::add(self.0.to_runtime(), rhs.0.to_runtime()).map(Value::from_runtime)
345 }
346 #[inline]
347 pub fn sub(&self, rhs: &Value) -> Result<Value, String> {
348 if let Some(n) = self.0.int_sub(&rhs.0) {
349 return Ok(Value(n));
350 }
351 arith::subtract(self.0.to_runtime(), rhs.0.to_runtime()).map(Value::from_runtime)
352 }
353 #[inline]
354 pub fn mul(&self, rhs: &Value) -> Result<Value, String> {
355 if let Some(n) = self.0.int_mul(&rhs.0) {
356 return Ok(Value(n));
357 }
358 arith::multiply(self.0.to_runtime(), rhs.0.to_runtime()).map(Value::from_runtime)
359 }
360
361 #[inline]
364 pub fn lt(&self, rhs: &Value) -> Result<Value, String> {
365 if let Some(b) = self.0.int_lt(&rhs.0) {
366 return Ok(Value::bool(b));
367 }
368 compare::compare(BinaryOpKind::Lt, &self.0.to_runtime(), &rhs.0.to_runtime())
369 .map(Value::from_runtime)
370 }
371 #[inline]
372 pub fn gt(&self, rhs: &Value) -> Result<Value, String> {
373 if let (Some(a), Some(b)) = (self.0.as_inline_int(), rhs.0.as_inline_int()) {
374 return Ok(Value::bool(a > b));
375 }
376 compare::compare(BinaryOpKind::Gt, &self.0.to_runtime(), &rhs.0.to_runtime())
377 .map(Value::from_runtime)
378 }
379 #[inline]
380 pub fn lte(&self, rhs: &Value) -> Result<Value, String> {
381 if let (Some(a), Some(b)) = (self.0.as_inline_int(), rhs.0.as_inline_int()) {
382 return Ok(Value::bool(a <= b));
383 }
384 compare::compare(BinaryOpKind::LtEq, &self.0.to_runtime(), &rhs.0.to_runtime())
385 .map(Value::from_runtime)
386 }
387 #[inline]
388 pub fn gte(&self, rhs: &Value) -> Result<Value, String> {
389 if let (Some(a), Some(b)) = (self.0.as_inline_int(), rhs.0.as_inline_int()) {
390 return Ok(Value::bool(a >= b));
391 }
392 compare::compare(BinaryOpKind::GtEq, &self.0.to_runtime(), &rhs.0.to_runtime())
393 .map(Value::from_runtime)
394 }
395 #[inline]
396 pub fn eq_op(&self, rhs: &Value) -> Value {
397 if let Some(b) = self.0.int_eq(&rhs.0) {
398 return Value::bool(b);
399 }
400 Value::bool(self.values_equal(rhs))
401 }
402 #[inline]
403 pub fn neq_op(&self, rhs: &Value) -> Value {
404 if let Some(b) = self.0.int_eq(&rhs.0) {
405 return Value::bool(!b);
406 }
407 Value::bool(!self.values_equal(rhs))
408 }
409 }
410}
411
412pub use repr::Value;
413
414impl Value {
419 #[inline]
420 pub fn text(s: String) -> Self {
421 Value::from_runtime(RuntimeValue::Text(Rc::new(s)))
422 }
423
424 #[inline]
425 pub fn is_truthy(&self) -> bool {
426 if let Some(b) = self.as_bool() {
429 return b;
430 }
431 if let Some(n) = self.as_int() {
432 return n != 0;
433 }
434 self.as_runtime().is_truthy()
435 }
436 #[inline]
437 pub fn to_display_string(&self) -> String {
438 self.as_runtime().to_display_string()
439 }
440 #[inline]
445 pub fn type_name(&self) -> &str {
446 if self.as_bool().is_some() {
449 return "Bool";
450 }
451 if self.as_int().is_some() {
452 return "Int";
453 }
454 if self.as_float().is_some() {
455 return "Float";
456 }
457 match self.as_runtime_ref() {
458 Some(rv) => rv.type_name(),
459 None => "Nothing",
460 }
461 }
462
463 #[inline]
466 pub fn values_equal(&self, other: &Value) -> bool {
467 compare::values_equal(&self.as_runtime(), &other.as_runtime())
468 }
469
470 pub fn list(items: Vec<Value>) -> Self {
473 let values: Vec<RuntimeValue> = items.into_iter().map(Value::into_runtime).collect();
474 Value::from_runtime(RuntimeValue::List(Rc::new(RefCell::new(
475 crate::interpreter::ListRepr::from_values(values),
476 ))))
477 }
478 pub fn int_list(items: Vec<i64>) -> Self {
481 Value::from_runtime(RuntimeValue::List(Rc::new(RefCell::new(
482 crate::interpreter::ListRepr::Ints(items),
483 ))))
484 }
485
486 pub fn empty_list() -> Self {
487 Value::from_runtime(RuntimeValue::List(Rc::new(RefCell::new(
488 crate::interpreter::ListRepr::Ints(Vec::new()),
489 ))))
490 }
491 pub fn empty_list_i32() -> Self {
494 Value::from_runtime(RuntimeValue::List(Rc::new(RefCell::new(
495 crate::interpreter::ListRepr::IntsI32(Vec::new()),
496 ))))
497 }
498 pub fn empty_set() -> Self {
499 Value::from_runtime(RuntimeValue::Set(Rc::new(RefCell::new(Vec::new()))))
500 }
501 pub fn empty_map() -> Self {
502 Value::from_runtime(RuntimeValue::Map(Rc::new(RefCell::new(
503 crate::interpreter::MapStorage::default(),
504 ))))
505 }
506 pub fn int_range(lo: i64, hi: i64) -> Self {
507 Value::from_runtime(RuntimeValue::List(Rc::new(RefCell::new(
508 crate::interpreter::ListRepr::Ints((lo..=hi).collect()),
509 ))))
510 }
511
512 pub fn list_push(&self, value: Value) -> Result<(), String> {
513 collections::list_push(&self.as_runtime(), value.into_runtime())
514 }
515
516 pub fn len(&self) -> Result<i64, String> {
517 match collections::length_of(&self.as_runtime())? {
518 RuntimeValue::Int(n) => Ok(n),
519 _ => unreachable!("length_of always returns Int"),
520 }
521 }
522
523 pub fn index_get(&self, idx: &Value) -> Result<Value, String> {
524 collections::index_get(&self.as_runtime(), &idx.as_runtime()).map(Value::from_runtime)
525 }
526
527 pub fn set_add(&self, value: Value) -> Result<(), String> {
528 collections::set_add(&self.as_runtime(), value.into_runtime())
529 }
530
531 pub fn remove_from(&self, value: &Value) -> Result<(), String> {
532 collections::remove_from(&self.as_runtime(), &value.as_runtime())
533 }
534
535 pub fn index_set(&self, idx: &Value, value: Value) -> Result<(), String> {
536 collections::index_set(&self.as_runtime(), &idx.as_runtime(), value.into_runtime())
537 }
538
539 pub fn contains(&self, value: &Value) -> Result<bool, String> {
540 match collections::contains(&self.as_runtime(), &value.as_runtime())? {
541 RuntimeValue::Bool(b) => Ok(b),
542 _ => unreachable!("contains always returns Bool"),
543 }
544 }
545
546 pub fn div(&self, rhs: &Value) -> Result<Value, String> {
549 arith::divide(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
550 }
551
552 pub fn exact_div(&self, rhs: &Value) -> Result<Value, String> {
556 arith::exact_divide(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
557 }
558
559 pub fn floor_div(&self, rhs: &Value) -> Result<Value, String> {
563 arith::floor_divide(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
564 }
565
566 pub fn modulo(&self, rhs: &Value) -> Result<Value, String> {
567 arith::modulo(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
568 }
569
570 pub fn concat(&self, rhs: &Value) -> Result<Value, String> {
571 arith::concat(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
572 }
573
574 pub fn seq_concat(&self, rhs: &Value) -> Result<Value, String> {
576 arith::seq_concat(self.runtime_owned(), rhs.runtime_owned()).map(Value::from_runtime)
577 }
578
579 pub fn pow(&self, rhs: &Value) -> Result<Value, String> {
580 arith::binary_op(BinaryOpKind::Pow, self.runtime_owned(), rhs.runtime_owned())
581 .map(Value::from_runtime)
582 }
583
584 pub fn bitxor(&self, rhs: &Value) -> Result<Value, String> {
585 arith::binary_op(BinaryOpKind::BitXor, self.runtime_owned(), rhs.runtime_owned())
586 .map(Value::from_runtime)
587 }
588
589 pub fn bitand(&self, rhs: &Value) -> Result<Value, String> {
590 arith::binary_op(BinaryOpKind::BitAnd, self.runtime_owned(), rhs.runtime_owned())
591 .map(Value::from_runtime)
592 }
593
594 pub fn bitor(&self, rhs: &Value) -> Result<Value, String> {
595 arith::binary_op(BinaryOpKind::BitOr, self.runtime_owned(), rhs.runtime_owned())
596 .map(Value::from_runtime)
597 }
598
599 pub fn shl(&self, rhs: &Value) -> Result<Value, String> {
600 arith::binary_op(BinaryOpKind::Shl, self.runtime_owned(), rhs.runtime_owned())
601 .map(Value::from_runtime)
602 }
603
604 pub fn shr(&self, rhs: &Value) -> Result<Value, String> {
605 arith::binary_op(BinaryOpKind::Shr, self.runtime_owned(), rhs.runtime_owned())
606 .map(Value::from_runtime)
607 }
608
609 pub fn not_op(&self) -> Result<Value, String> {
611 arith::not_value(self.runtime_owned()).map(Value::from_runtime)
612 }
613
614 #[inline]
618 fn runtime_owned(&self) -> RuntimeValue {
619 self.as_runtime().clone()
620 }
621}
622
623#[cfg(test)]
624mod fast_path_kernel_differential {
625 use super::*;
631
632 const EDGES: [i64; 12] = [
633 i64::MIN,
634 i64::MIN + 1,
635 -4611686018427387904, -1000003,
637 -2,
638 -1,
639 0,
640 1,
641 2,
642 1000003,
643 4611686018427387903, i64::MAX,
645 ];
646
647 fn assert_same(op_name: &str, a: i64, b: i64, fast: Result<Value, String>, kernel: Result<RuntimeValue, String>) {
648 match (fast, kernel) {
649 (Ok(f), Ok(k)) => assert_eq!(
650 f.into_runtime(),
651 k,
652 "{op_name}({a}, {b}) fast path diverged from kernel"
653 ),
654 (Err(f), Err(k)) => assert_eq!(f, k, "{op_name}({a}, {b}) error strings diverged"),
655 (f, k) => panic!("{op_name}({a}, {b}): fast {f:?} vs kernel {k:?}"),
656 }
657 }
658
659 #[test]
660 fn int_arith_matches_kernel_over_edge_grid() {
661 for &a in &EDGES {
662 for &b in &EDGES {
663 let (va, vb) = (Value::int(a), Value::int(b));
664 assert_same("add", a, b, va.add(&vb), arith::add(RuntimeValue::Int(a), RuntimeValue::Int(b)));
665 assert_same("sub", a, b, va.sub(&vb), arith::subtract(RuntimeValue::Int(a), RuntimeValue::Int(b)));
666 assert_same("mul", a, b, va.mul(&vb), arith::multiply(RuntimeValue::Int(a), RuntimeValue::Int(b)));
667 }
668 }
669 }
670
671 #[test]
672 fn int_comparisons_match_kernel_over_edge_grid() {
673 for &a in &EDGES {
674 for &b in &EDGES {
675 let (va, vb) = (Value::int(a), Value::int(b));
676 assert_same("lt", a, b, va.lt(&vb), compare::compare(BinaryOpKind::Lt, &RuntimeValue::Int(a), &RuntimeValue::Int(b)));
677 assert_same("gt", a, b, va.gt(&vb), compare::compare(BinaryOpKind::Gt, &RuntimeValue::Int(a), &RuntimeValue::Int(b)));
678 assert_same("lte", a, b, va.lte(&vb), compare::compare(BinaryOpKind::LtEq, &RuntimeValue::Int(a), &RuntimeValue::Int(b)));
679 assert_same("gte", a, b, va.gte(&vb), compare::compare(BinaryOpKind::GtEq, &RuntimeValue::Int(a), &RuntimeValue::Int(b)));
680 assert_eq!(
681 va.eq_op(&vb).is_truthy(),
682 compare::values_equal(&RuntimeValue::Int(a), &RuntimeValue::Int(b)),
683 "eq({a}, {b}) diverged"
684 );
685 assert_eq!(
686 va.neq_op(&vb).is_truthy(),
687 !compare::values_equal(&RuntimeValue::Int(a), &RuntimeValue::Int(b)),
688 "neq({a}, {b}) diverged"
689 );
690 }
691 }
692 }
693
694 #[test]
695 fn value_width_matches_the_active_representation() {
696 let w = std::mem::size_of::<Value>();
700 #[cfg(feature = "narrow-value")]
701 assert_eq!(w, 8, "narrow Value must be 8 bytes (NaN-boxed u64)");
702 #[cfg(not(feature = "narrow-value"))]
703 assert_eq!(w, 16, "default Value must be 16 bytes (RuntimeValue newtype)");
704 }
705
706 #[test]
707 fn mixed_operands_still_route_through_the_kernel() {
708 let sum = Value::int(2).add(&Value::float(0.5)).unwrap();
711 assert_eq!(sum.into_runtime(), RuntimeValue::Float(2.5));
712 assert!(Value::int(2).lt(&Value::float(2.5)).unwrap().is_truthy());
713 let fast = Value::int(2).add(&Value::text("x".into()));
715 let kernel = arith::add(RuntimeValue::Int(2), RuntimeValue::Text(Rc::new("x".into())));
716 assert_eq!(fast.map(Value::into_runtime), kernel);
717 let fast = Value::int(2).mul(&Value::bool(true));
719 let kernel = arith::multiply(RuntimeValue::Int(2), RuntimeValue::Bool(true));
720 assert_eq!(fast.map(Value::into_runtime), kernel);
721 assert!(matches!(Value::int(2).mul(&Value::bool(true)), Err(_)));
722 }
723}
724
725#[cfg(test)]
726mod value_comparison_tests {
727 use super::*;
728
729 #[test]
730 fn float_relational_is_ieee() {
731 assert!(!Value::float(-0.0).lt(&Value::float(0.0)).unwrap().is_truthy());
733 assert!(Value::float(-0.0).lte(&Value::float(0.0)).unwrap().is_truthy());
734 let nan = Value::float(f64::NAN);
736 assert!(!nan.lt(&Value::float(1.0)).unwrap().is_truthy());
737 assert!(!nan.gt(&Value::float(1.0)).unwrap().is_truthy());
738 assert!(!nan.lte(&nan).unwrap().is_truthy());
739 assert!(!Value::float(1.0).gte(&nan).unwrap().is_truthy());
740 assert!(Value::float(1.5).lt(&Value::float(2.5)).unwrap().is_truthy());
742 assert!(Value::int(2).lt(&Value::float(2.5)).unwrap().is_truthy());
743 assert!(Value::int(5).gte(&Value::int(5)).unwrap().is_truthy());
744 }
745
746 #[test]
747 fn float_equality_matches_treewalker() {
748 assert!(!Value::float(f64::NAN).eq_op(&Value::float(f64::NAN)).is_truthy());
750 assert!(Value::float(f64::NAN).neq_op(&Value::float(f64::NAN)).is_truthy());
751 assert!(Value::float(1.5).eq_op(&Value::float(1.5)).is_truthy());
753 let sum = Value::float(0.1).add(&Value::float(0.2)).unwrap(); assert!(!sum.eq_op(&Value::float(0.3)).is_truthy());
757 assert!(sum.eq_op(&Value::float(0.30000000000000004)).is_truthy());
758 assert!(!Value::float(1.0).eq_op(&Value::float(2.0)).is_truthy());
760 assert!(Value::int(5).eq_op(&Value::int(5)).is_truthy());
762 assert!(Value::text("hi".to_string()).eq_op(&Value::text("hi".to_string())).is_truthy());
763 assert!(!Value::int(5).eq_op(&Value::int(6)).is_truthy());
764 }
765}