1use std::rc::Rc;
4
5use logicaffeine_base::{BigInt, Complex, Decimal, Modular, Rational, WordVal};
6
7use crate::ast::stmt::BinaryOpKind;
8use crate::interpreter::RuntimeValue;
9
10use super::compare::{compare, values_equal};
11use super::temporal::date_add_span;
12
13fn big_of(v: &RuntimeValue) -> Option<BigInt> {
16 match v {
17 RuntimeValue::Int(n) => Some(BigInt::from_i64(*n)),
18 RuntimeValue::BigInt(b) => Some((**b).clone()),
19 _ => None,
20 }
21}
22
23fn rat_of(v: &RuntimeValue) -> Option<Rational> {
27 match v {
28 RuntimeValue::Int(n) => Some(Rational::from_i64(*n)),
29 RuntimeValue::BigInt(b) => Some(Rational::from_bigint((**b).clone())),
30 RuntimeValue::Rational(r) => Some((**r).clone()),
31 RuntimeValue::Decimal(d) => Some(d.to_rational()),
32 _ => None,
33 }
34}
35
36fn dec_of(v: &RuntimeValue) -> Option<Decimal> {
40 match v {
41 RuntimeValue::Int(n) => Some(Decimal::from_i64(*n)),
42 RuntimeValue::BigInt(b) => Some(Decimal::from_bigint((**b).clone())),
43 RuntimeValue::Decimal(d) => Some((**d).clone()),
44 _ => None,
45 }
46}
47
48fn num_f64(v: &RuntimeValue) -> Option<f64> {
51 match v {
52 RuntimeValue::Int(n) => Some(*n as f64),
53 RuntimeValue::BigInt(b) => Some(b.to_f64()),
54 RuntimeValue::Rational(r) => Some(r.to_f64()),
55 RuntimeValue::Decimal(d) => Some(d.to_rational().to_f64()),
56 RuntimeValue::Float(f) => Some(*f),
57 _ => None,
58 }
59}
60
61fn complex_of(v: &RuntimeValue) -> Option<Complex> {
65 match v {
66 RuntimeValue::Int(n) => Some(Complex::from_i64(*n)),
67 RuntimeValue::BigInt(b) => Some(Complex::from_rational(Rational::from_bigint((**b).clone()))),
68 RuntimeValue::Rational(r) => Some(Complex::from_rational((**r).clone())),
69 RuntimeValue::Decimal(d) => Some(Complex::from_rational(d.to_rational())),
70 RuntimeValue::Complex(c) => Some((**c).clone()),
71 _ => None,
72 }
73}
74
75fn modular_binop(
80 left: &RuntimeValue,
81 right: &RuntimeValue,
82 op_name: &str,
83 f: impl Fn(&Modular, &Modular) -> Option<Modular>,
84) -> Option<Result<RuntimeValue, String>> {
85 if !matches!(left, RuntimeValue::Modular(_)) && !matches!(right, RuntimeValue::Modular(_)) {
86 return None;
87 }
88 Some(match (left, right) {
89 (RuntimeValue::Modular(a), RuntimeValue::Modular(b)) => match f(a, b) {
90 Some(r) => Ok(RuntimeValue::Modular(Rc::new(r))),
91 None => Err(format!("cannot {op_name} values in different modular rings")),
92 },
93 _ => Err(format!(
94 "Cannot {} {} and {} (modular arithmetic needs two ℤ/nℤ values of the same modulus)",
95 op_name,
96 left.type_name(),
97 right.type_name()
98 )),
99 })
100}
101
102fn complex_binop(
106 left: &RuntimeValue,
107 right: &RuntimeValue,
108 op_name: &str,
109 f: impl Fn(&Complex, &Complex) -> Complex,
110) -> Option<Result<RuntimeValue, String>> {
111 if !matches!(left, RuntimeValue::Complex(_)) && !matches!(right, RuntimeValue::Complex(_)) {
112 return None;
113 }
114 Some(match (complex_of(left), complex_of(right)) {
115 (Some(a), Some(b)) => Ok(RuntimeValue::Complex(Rc::new(f(&a, &b)))),
116 _ => Err(format!(
117 "Cannot {} {} and {} (Complex combines only with exact numbers)",
118 op_name,
119 left.type_name(),
120 right.type_name()
121 )),
122 })
123}
124
125fn decimal_binop(
130 left: &RuntimeValue,
131 right: &RuntimeValue,
132 dec: impl Fn(&Decimal, &Decimal) -> Decimal,
133 rat: impl Fn(&Rational, &Rational) -> Rational,
134 flt: impl Fn(f64, f64) -> f64,
135) -> Option<RuntimeValue> {
136 if !matches!(left, RuntimeValue::Decimal(_)) && !matches!(right, RuntimeValue::Decimal(_)) {
137 return None;
138 }
139 if let (Some(a), Some(b)) = (dec_of(left), dec_of(right)) {
140 return Some(RuntimeValue::Decimal(Rc::new(dec(&a, &b))));
141 }
142 if let (Some(a), Some(b)) = (rat_of(left), rat_of(right)) {
143 return Some(RuntimeValue::from_rational(rat(&a, &b)));
144 }
145 let (a, b) = (num_f64(left)?, num_f64(right)?);
146 Some(RuntimeValue::Float(flt(a, b)))
147}
148
149fn quantity_binop(left: &RuntimeValue, right: &RuntimeValue, op: char) -> Option<Result<RuntimeValue, String>> {
155 use crate::interpreter::QuantityValue;
156 use logicaffeine_base::{Quantity, Unit};
157 if !matches!(left, RuntimeValue::Quantity(_)) && !matches!(right, RuntimeValue::Quantity(_)) {
158 return None;
159 }
160 let mk = |q: Quantity, unit: Unit| RuntimeValue::Quantity(Rc::new(QuantityValue { q, unit }));
161 let si_unit = |q: &Quantity| Unit::linear("", q.dimension(), Rational::one());
164 Some(match (left, right) {
165 (RuntimeValue::Quantity(a), RuntimeValue::Quantity(b)) => match op {
166 '+' => a.q.add(&b.q).map(|q| mk(q, a.unit.clone())).ok_or_else(|| {
167 format!("cannot add quantities of different dimensions ({} vs {})", a.q.dimension(), b.q.dimension())
168 }),
169 '-' => a.q.sub(&b.q).map(|q| mk(q, a.unit.clone())).ok_or_else(|| {
170 format!("cannot subtract quantities of different dimensions ({} vs {})", a.q.dimension(), b.q.dimension())
171 }),
172 '*' => {
173 let q = a.q.mul(&b.q);
174 let u = si_unit(&q);
175 Ok(mk(q, u))
176 }
177 '/' => match a.q.div(&b.q) {
178 Some(q) => {
179 let u = si_unit(&q);
180 Ok(mk(q, u))
181 }
182 None => Err("cannot divide by a zero quantity".to_string()),
183 },
184 _ => unreachable!("quantity_binop only handles + - * /"),
185 },
186 (RuntimeValue::Quantity(a), scalar) if matches!(op, '*' | '/') => match rat_of(scalar) {
188 Some(k) => {
189 let mag = if op == '*' {
190 a.q.magnitude_si().mul(&k)
191 } else {
192 match a.q.magnitude_si().div(&k) {
193 Some(m) => m,
194 None => return Some(Err("cannot divide a quantity by zero".to_string())),
195 }
196 };
197 Ok(mk(Quantity::si(mag, a.q.dimension()), a.unit.clone()))
198 }
199 None => return None,
200 },
201 (scalar, RuntimeValue::Quantity(b)) if op == '*' => match rat_of(scalar) {
203 Some(k) => Ok(mk(Quantity::si(b.q.magnitude_si().mul(&k), b.q.dimension()), b.unit.clone())),
204 None => return None,
205 },
206 _ => return None,
207 })
208}
209
210fn money_binop(left: &RuntimeValue, right: &RuntimeValue, op: char) -> Option<Result<RuntimeValue, String>> {
215 use logicaffeine_base::{Decimal, Money, RoundingMode};
216 if !matches!(left, RuntimeValue::Money(_)) && !matches!(right, RuntimeValue::Money(_)) {
217 return None;
218 }
219 let dec_of = |v: &RuntimeValue| -> Option<Decimal> {
221 match v {
222 RuntimeValue::Int(n) => Some(Decimal::from_i64(*n)),
223 RuntimeValue::Decimal(d) => Some((**d).clone()),
224 _ => None,
225 }
226 };
227 let mk = |m: Money| RuntimeValue::Money(Rc::new(m));
228 let mismatch = |a: &Money, b: &Money, verb: &str| {
229 format!("cannot {verb} money of different currencies ({} vs {})", a.currency.code, b.currency.code)
230 };
231 Some(match (left, right) {
232 (RuntimeValue::Money(a), RuntimeValue::Money(b)) => match op {
233 '+' => a.add(b).map(mk).ok_or_else(|| mismatch(a, b, "add")),
234 '-' => a.sub(b).map(mk).ok_or_else(|| mismatch(a, b, "subtract")),
235 '/' => match a.ratio(b) {
236 Some(r) => Ok(RuntimeValue::from_rational(r)),
237 None if a.currency != b.currency => Err(mismatch(a, b, "compare")),
238 None => Err("cannot divide money by a zero amount".to_string()),
239 },
240 '*' => Err("cannot multiply money by money — scale by a number instead".to_string()),
241 _ => unreachable!("money_binop only handles + - * /"),
242 },
243 (RuntimeValue::Money(a), scalar) | (scalar, RuntimeValue::Money(a)) if op == '*' => {
245 match dec_of(scalar) {
246 Some(s) => Ok(mk(Money::of(a.amount.mul(&s), a.currency))),
247 None => Err(format!("cannot multiply money by {}", scalar.type_name())),
248 }
249 }
250 (RuntimeValue::Money(a), scalar) if op == '/' => match dec_of(scalar) {
252 Some(s) => match a.amount.div(&s, a.currency.scale, RoundingMode::HalfEven) {
253 Some(d) => Ok(mk(Money::of(d, a.currency))),
254 None => Err("cannot divide money by zero".to_string()),
255 },
256 None => Err(format!("cannot divide money by {}", scalar.type_name())),
257 },
258 _ => return None,
259 })
260}
261
262fn word_width_err(a: WordVal, b: WordVal) -> String {
271 format!("cannot combine Word{} and Word{} — width mismatch", a.width(), b.width())
272}
273
274fn shift_count(v: &RuntimeValue) -> Option<u32> {
276 match v {
277 RuntimeValue::Int(n) => Some(*n as u32),
278 RuntimeValue::Word(w) => Some(w.to_u64() as u32),
279 _ => None,
280 }
281}
282
283fn word_binary_op(
287 op: BinaryOpKind,
288 left: &RuntimeValue,
289 right: &RuntimeValue,
290) -> Result<Option<RuntimeValue>, String> {
291 use BinaryOpKind::*;
292 if matches!(op, Shl | Shr) {
293 if let RuntimeValue::Word(a) = left {
294 let n = shift_count(right).ok_or_else(|| "shift count must be an integer".to_string())?;
295 let r = if matches!(op, Shl) { a.shl(n) } else { a.shr(n) };
296 return Ok(Some(RuntimeValue::Word(r)));
297 }
298 return Ok(None);
299 }
300 let (RuntimeValue::Word(a), RuntimeValue::Word(b)) = (left, right) else {
301 return Ok(None);
302 };
303 let combined = match op {
304 And => a.bitand(*b),
307 Or => a.bitor(*b),
308 BitXor => a.bitxor(*b),
309 _ => return Ok(None),
310 };
311 match combined {
312 Some(w) => Ok(Some(RuntimeValue::Word(w))),
313 None => Err(word_width_err(*a, *b)),
314 }
315}
316
317fn lanes_binary_op(
321 op: BinaryOpKind,
322 left: &RuntimeValue,
323 right: &RuntimeValue,
324) -> Result<Option<RuntimeValue>, String> {
325 let (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) = (left, right) else {
326 return Ok(None);
327 };
328 let combined = match op {
329 BinaryOpKind::BitXor => a.bitxor(**b),
330 _ => return Ok(None),
331 };
332 match combined {
333 Some(v) => Ok(Some(RuntimeValue::Lanes(std::rc::Rc::new(v)))),
334 None => Err(format!(
335 "cannot combine {} and {} — lane-config mismatch",
336 a.type_name(),
337 b.type_name()
338 )),
339 }
340}
341
342pub fn binary_op(
343 op: BinaryOpKind,
344 left: RuntimeValue,
345 right: RuntimeValue,
346) -> Result<RuntimeValue, String> {
347 if let Some(result) = word_binary_op(op, &left, &right)? {
350 return Ok(result);
351 }
352 if let Some(result) = lanes_binary_op(op, &left, &right)? {
354 return Ok(result);
355 }
356 match op {
357 BinaryOpKind::Add => add(left, right),
358 BinaryOpKind::Subtract => subtract(left, right),
359 BinaryOpKind::Multiply => multiply(left, right),
360 BinaryOpKind::Pow => power(left, right),
361 BinaryOpKind::Divide => divide(left, right),
362 BinaryOpKind::ExactDivide => exact_divide(left, right),
363 BinaryOpKind::FloorDivide => floor_divide(left, right),
364 BinaryOpKind::Modulo => modulo(left, right),
365 BinaryOpKind::Eq => Ok(RuntimeValue::Bool(values_equal(&left, &right))),
366 BinaryOpKind::NotEq => Ok(RuntimeValue::Bool(!values_equal(&left, &right))),
367 BinaryOpKind::ApproxEq => approx_eq(left, right),
368 BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq => {
369 compare(op, &left, &right)
370 }
371 BinaryOpKind::And => Ok(RuntimeValue::Bool(left.is_truthy() && right.is_truthy())),
373 BinaryOpKind::Or => Ok(RuntimeValue::Bool(left.is_truthy() || right.is_truthy())),
374 BinaryOpKind::Concat => concat(left, right),
375 BinaryOpKind::SeqConcat => seq_concat(left, right),
376 BinaryOpKind::BitXor => match (&left, &right) {
377 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Int(a ^ b)),
378 (RuntimeValue::Bool(a), RuntimeValue::Bool(b)) => Ok(RuntimeValue::Bool(a ^ b)),
381 (RuntimeValue::Set(_), RuntimeValue::Set(_)) => set_binop(&left, &right, SetOp::SymmetricDifference),
383 _ => Err("Bitwise XOR requires integer, boolean, or Set operands".to_string()),
384 },
385 BinaryOpKind::BitAnd => match (&left, &right) {
387 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Int(a & b)),
388 (RuntimeValue::Bool(a), RuntimeValue::Bool(b)) => Ok(RuntimeValue::Bool(a & b)),
389 (RuntimeValue::Set(_), RuntimeValue::Set(_)) => set_binop(&left, &right, SetOp::Intersection),
390 _ => Err("`&` requires integer, boolean, or Set operands".to_string()),
391 },
392 BinaryOpKind::BitOr => match (&left, &right) {
394 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Int(a | b)),
395 (RuntimeValue::Bool(a), RuntimeValue::Bool(b)) => Ok(RuntimeValue::Bool(a | b)),
396 (RuntimeValue::Set(_), RuntimeValue::Set(_)) => set_binop(&left, &right, SetOp::Union),
397 _ => Err("`|` requires integer, boolean, or Set operands".to_string()),
398 },
399 BinaryOpKind::Shl => match (left, right) {
401 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => {
402 Ok(RuntimeValue::Int(a.wrapping_shl(b as u32)))
403 }
404 _ => Err("Left shift requires integer operands".to_string()),
405 },
406 BinaryOpKind::Shr => match (left, right) {
407 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => {
408 Ok(RuntimeValue::Int(a.wrapping_shr(b as u32)))
409 }
410 _ => Err("Right shift requires integer operands".to_string()),
411 },
412 }
413}
414
415pub fn add(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
416 if let (RuntimeValue::Word(a), RuntimeValue::Word(b)) = (&left, &right) {
419 return a.add(*b).map(RuntimeValue::Word).ok_or_else(|| word_width_err(*a, *b));
420 }
421 if let (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) = (&left, &right) {
423 return a.add(**b).map(|v| RuntimeValue::Lanes(std::rc::Rc::new(v))).ok_or_else(|| {
424 format!("cannot add {} and {} — lane-config mismatch", a.type_name(), b.type_name())
425 });
426 }
427 if let Some(r) = quantity_binop(&left, &right, '+') {
428 return r;
429 }
430 if let Some(r) = money_binop(&left, &right, '+') {
431 return r;
432 }
433 if let Some(r) = modular_binop(&left, &right, "add", |a, b| a.add(b)) {
434 return r;
435 }
436 if let Some(r) = complex_binop(&left, &right, "add", |a, b| a.add(b)) {
437 return r;
438 }
439 if let Some(r) = decimal_binop(&left, &right, |a, b| a.add(b), |a, b| a.add(b), |a, b| a + b) {
440 return Ok(r);
441 }
442 match (&left, &right) {
443 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(match a.checked_add(*b) {
447 Some(s) => RuntimeValue::Int(s),
448 None => RuntimeValue::from_bigint(BigInt::from_i64(*a).add(&BigInt::from_i64(*b))),
449 }),
450 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::from_bigint(a.add(b))),
451 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
452 Ok(RuntimeValue::from_bigint(a.add(&BigInt::from_i64(*b))))
453 }
454 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
455 Ok(RuntimeValue::from_bigint(BigInt::from_i64(*a).add(b)))
456 }
457 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a + b)),
458 (RuntimeValue::Int(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(*a as f64 + b)),
459 (RuntimeValue::Float(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Float(a + *b as f64)),
460 (RuntimeValue::BigInt(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a.to_f64() + b)),
461 (RuntimeValue::Float(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::Float(a + b.to_f64())),
462 (RuntimeValue::Rational(a), RuntimeValue::Rational(b)) => {
466 Ok(RuntimeValue::from_rational(a.add(b)))
467 }
468 (RuntimeValue::Rational(_), RuntimeValue::Int(_))
469 | (RuntimeValue::Int(_), RuntimeValue::Rational(_))
470 | (RuntimeValue::Rational(_), RuntimeValue::BigInt(_))
471 | (RuntimeValue::BigInt(_), RuntimeValue::Rational(_)) => {
472 Ok(RuntimeValue::from_rational(rat_of(&left).unwrap().add(&rat_of(&right).unwrap())))
473 }
474 (RuntimeValue::Rational(r), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(r.to_f64() + b)),
475 (RuntimeValue::Float(a), RuntimeValue::Rational(r)) => Ok(RuntimeValue::Float(a + r.to_f64())),
476 (RuntimeValue::Text(a), RuntimeValue::Text(b)) => {
477 Ok(RuntimeValue::Text(Rc::new(format!("{}{}", a, b))))
478 }
479 (RuntimeValue::Text(a), other) => {
480 Ok(RuntimeValue::Text(Rc::new(format!("{}{}", a, other.to_display_string()))))
481 }
482 (other, RuntimeValue::Text(b)) => {
483 Ok(RuntimeValue::Text(Rc::new(format!("{}{}", other.to_display_string(), b))))
484 }
485 (RuntimeValue::Duration(a), RuntimeValue::Duration(b)) => {
486 Ok(RuntimeValue::Duration(a.wrapping_add(*b)))
487 }
488 (RuntimeValue::Date(days), RuntimeValue::Span { months, days: span_days }) => {
489 Ok(RuntimeValue::Date(date_add_span(*days, *months, *span_days)))
490 }
491 (RuntimeValue::Moment(nanos), RuntimeValue::Span { months, days: span_days })
494 | (RuntimeValue::Span { months, days: span_days }, RuntimeValue::Moment(nanos)) => Ok(
495 RuntimeValue::Moment(super::temporal::moment_add_span(*nanos, *months, *span_days)),
496 ),
497 (RuntimeValue::Moment(nanos), RuntimeValue::Duration(d))
499 | (RuntimeValue::Duration(d), RuntimeValue::Moment(nanos)) => {
500 Ok(RuntimeValue::Moment(nanos.wrapping_add(*d)))
501 }
502 (RuntimeValue::List(_), RuntimeValue::List(_)) => {
505 seq_concat(left.clone(), right.clone())
506 }
507 _ => Err(format!("Cannot add {} and {}", left.type_name(), right.type_name())),
508 }
509}
510
511pub fn concat(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
512 Ok(RuntimeValue::Text(Rc::new(format!(
513 "{}{}",
514 left.to_display_string(),
515 right.to_display_string()
516 ))))
517}
518
519enum SetOp {
523 Union,
524 Intersection,
525 Difference,
526 SymmetricDifference,
527}
528
529fn set_binop(left: &RuntimeValue, right: &RuntimeValue, op: SetOp) -> Result<RuntimeValue, String> {
530 let (RuntimeValue::Set(a), RuntimeValue::Set(b)) = (left, right) else {
531 return Err("set operation requires two Sets".to_string());
532 };
533 let (a, b) = (a.borrow(), b.borrow());
534 let contains = |xs: &[RuntimeValue], v: &RuntimeValue| xs.iter().any(|x| values_equal(x, v));
535 let mut out: Vec<RuntimeValue> = Vec::new();
536 match op {
537 SetOp::Union => {
538 out.extend(a.iter().cloned());
539 for v in b.iter() {
540 if !contains(&out, v) {
541 out.push(v.clone());
542 }
543 }
544 }
545 SetOp::Intersection => {
546 for v in a.iter() {
547 if contains(&b, v) {
548 out.push(v.clone());
549 }
550 }
551 }
552 SetOp::Difference => {
553 for v in a.iter() {
554 if !contains(&b, v) {
555 out.push(v.clone());
556 }
557 }
558 }
559 SetOp::SymmetricDifference => {
560 for v in a.iter() {
561 if !contains(&b, v) {
562 out.push(v.clone());
563 }
564 }
565 for v in b.iter() {
566 if !contains(&a, v) {
567 out.push(v.clone());
568 }
569 }
570 }
571 }
572 Ok(RuntimeValue::Set(Rc::new(std::cell::RefCell::new(out))))
573}
574
575pub fn approx_eq(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
580 let as_f64 = |v: &RuntimeValue| -> Option<f64> {
581 match v {
582 RuntimeValue::Float(f) => Some(*f),
583 RuntimeValue::Int(n) => Some(*n as f64),
584 RuntimeValue::BigInt(b) => Some(b.to_f64()),
585 RuntimeValue::Rational(r) => Some(r.to_f64()),
586 _ => None,
587 }
588 };
589 match (as_f64(&left), as_f64(&right)) {
590 (Some(a), Some(b)) => Ok(RuntimeValue::Bool(logicaffeine_data::ops::logos_approx_eq(a, b))),
591 _ => Err(format!(
592 "`is approximately` compares numbers, got {} and {}",
593 left.type_name(),
594 right.type_name()
595 )),
596 }
597}
598
599pub fn seq_concat(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
602 use crate::interpreter::ListRepr;
603 match (&left, &right) {
604 (RuntimeValue::List(a), RuntimeValue::List(b)) => {
605 let mut items = a.borrow().to_values();
606 items.extend(b.borrow().to_values());
607 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(ListRepr::from_values(items)))))
608 }
609 _ => Err("`followed by` requires two sequences (merge two sequences into one)".to_string()),
610 }
611}
612
613pub fn subtract(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
614 if matches!((&left, &right), (RuntimeValue::Set(_), RuntimeValue::Set(_))) {
616 return set_binop(&left, &right, SetOp::Difference);
617 }
618 if let (RuntimeValue::Word(a), RuntimeValue::Word(b)) = (&left, &right) {
619 return a.sub(*b).map(RuntimeValue::Word).ok_or_else(|| word_width_err(*a, *b));
620 }
621 if let (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) = (&left, &right) {
623 return a.sub(**b).map(|v| RuntimeValue::Lanes(std::rc::Rc::new(v))).ok_or_else(|| {
624 format!("cannot subtract {} and {} — lane-config mismatch", a.type_name(), b.type_name())
625 });
626 }
627 if let Some(r) = quantity_binop(&left, &right, '-') {
628 return r;
629 }
630 if let Some(r) = money_binop(&left, &right, '-') {
631 return r;
632 }
633 if let Some(r) = modular_binop(&left, &right, "subtract", |a, b| a.sub(b)) {
634 return r;
635 }
636 if let Some(r) = complex_binop(&left, &right, "subtract", |a, b| a.sub(b)) {
637 return r;
638 }
639 if let Some(r) = decimal_binop(&left, &right, |a, b| a.sub(b), |a, b| a.sub(b), |a, b| a - b) {
640 return Ok(r);
641 }
642 match (&left, &right) {
643 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(match a.checked_sub(*b) {
644 Some(s) => RuntimeValue::Int(s),
645 None => RuntimeValue::from_bigint(BigInt::from_i64(*a).sub(&BigInt::from_i64(*b))),
646 }),
647 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::from_bigint(a.sub(b))),
648 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
649 Ok(RuntimeValue::from_bigint(a.sub(&BigInt::from_i64(*b))))
650 }
651 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
652 Ok(RuntimeValue::from_bigint(BigInt::from_i64(*a).sub(b)))
653 }
654 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a - b)),
655 (RuntimeValue::Int(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(*a as f64 - b)),
656 (RuntimeValue::Float(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Float(a - *b as f64)),
657 (RuntimeValue::BigInt(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a.to_f64() - b)),
658 (RuntimeValue::Float(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::Float(a - b.to_f64())),
659 (RuntimeValue::Rational(a), RuntimeValue::Rational(b)) => {
660 Ok(RuntimeValue::from_rational(a.sub(b)))
661 }
662 (RuntimeValue::Rational(_), RuntimeValue::Int(_))
663 | (RuntimeValue::Int(_), RuntimeValue::Rational(_))
664 | (RuntimeValue::Rational(_), RuntimeValue::BigInt(_))
665 | (RuntimeValue::BigInt(_), RuntimeValue::Rational(_)) => {
666 Ok(RuntimeValue::from_rational(rat_of(&left).unwrap().sub(&rat_of(&right).unwrap())))
667 }
668 (RuntimeValue::Rational(r), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(r.to_f64() - b)),
669 (RuntimeValue::Float(a), RuntimeValue::Rational(r)) => Ok(RuntimeValue::Float(a - r.to_f64())),
670 (RuntimeValue::Duration(a), RuntimeValue::Duration(b)) => {
671 Ok(RuntimeValue::Duration(a.wrapping_sub(*b)))
672 }
673 (RuntimeValue::Date(days), RuntimeValue::Span { months, days: span_days }) => {
674 Ok(RuntimeValue::Date(date_add_span(*days, -*months, -*span_days)))
675 }
676 (RuntimeValue::Moment(nanos), RuntimeValue::Span { months, days: span_days }) => Ok(
678 RuntimeValue::Moment(super::temporal::moment_add_span(*nanos, -*months, -*span_days)),
679 ),
680 (RuntimeValue::Moment(nanos), RuntimeValue::Duration(d)) => {
684 Ok(RuntimeValue::Moment(nanos.wrapping_sub(*d)))
685 }
686 _ => Err(format!(
687 "Cannot subtract {} from {}",
688 right.type_name(),
689 left.type_name()
690 )),
691 }
692}
693
694pub fn power(base: RuntimeValue, exp: RuntimeValue) -> Result<RuntimeValue, String> {
700 if matches!(base, RuntimeValue::Float(_)) || matches!(exp, RuntimeValue::Float(_)) {
702 let b = num_f64(&base)
703 .ok_or_else(|| format!("cannot raise {} to a power", base.type_name()))?;
704 let e = num_f64(&exp)
705 .ok_or_else(|| format!("cannot raise to a {} power", exp.type_name()))?;
706 return Ok(RuntimeValue::Float(b.powf(e)));
707 }
708 match (&base, &exp) {
709 (RuntimeValue::Int(b), RuntimeValue::Int(e)) => int_power(*b, *e),
710 (RuntimeValue::BigInt(b), RuntimeValue::Int(e)) => {
711 let ue = u32::try_from(*e)
712 .map_err(|_| "negative or too-large exponent on an integer (use a Float base)".to_string())?;
713 Ok(RuntimeValue::from_bigint(b.pow(ue)))
714 }
715 (RuntimeValue::Rational(b), RuntimeValue::Int(e)) => {
716 let ie = i32::try_from(*e).map_err(|_| "exponent too large".to_string())?;
717 b.pow(ie)
718 .map(RuntimeValue::from_rational)
719 .ok_or_else(|| "zero raised to a negative power".to_string())
720 }
721 _ => Err(format!(
722 "cannot raise {} to the {} power",
723 base.type_name(),
724 exp.type_name()
725 )),
726 }
727}
728
729fn int_power(base: i64, exp: i64) -> Result<RuntimeValue, String> {
732 if exp < 0 {
733 return Err(
734 "negative exponent on an integer (an Int can't hold a fraction — use a Float base)"
735 .to_string(),
736 );
737 }
738 let e = u32::try_from(exp).map_err(|_| "exponent too large".to_string())?;
739 match base.checked_pow(e) {
740 Some(r) => Ok(RuntimeValue::Int(r)),
741 None => Ok(RuntimeValue::from_bigint(BigInt::from_i64(base).pow(e))),
742 }
743}
744
745pub fn multiply(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
746 if let (RuntimeValue::Word(a), RuntimeValue::Word(b)) = (&left, &right) {
747 return a.mul(*b).map(RuntimeValue::Word).ok_or_else(|| word_width_err(*a, *b));
748 }
749 if let (RuntimeValue::Lanes(a), RuntimeValue::Lanes(b)) = (&left, &right) {
751 return a.mullo(**b).map(|v| RuntimeValue::Lanes(std::rc::Rc::new(v))).ok_or_else(|| {
752 format!("cannot multiply {} and {} — lane op undefined", a.type_name(), b.type_name())
753 });
754 }
755 if let Some(r) = quantity_binop(&left, &right, '*') {
756 return r;
757 }
758 if let Some(r) = money_binop(&left, &right, '*') {
759 return r;
760 }
761 if let Some(r) = modular_binop(&left, &right, "multiply", |a, b| a.mul(b)) {
762 return r;
763 }
764 if let Some(r) = complex_binop(&left, &right, "multiply", |a, b| a.mul(b)) {
765 return r;
766 }
767 if let Some(r) = decimal_binop(&left, &right, |a, b| a.mul(b), |a, b| a.mul(b), |a, b| a * b) {
768 return Ok(r);
769 }
770 match (&left, &right) {
771 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => Ok(match a.checked_mul(*b) {
772 Some(p) => RuntimeValue::Int(p),
773 None => RuntimeValue::from_bigint(BigInt::from_i64(*a).mul(&BigInt::from_i64(*b))),
774 }),
775 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::from_bigint(a.mul(b))),
776 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
777 Ok(RuntimeValue::from_bigint(a.mul(&BigInt::from_i64(*b))))
778 }
779 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
780 Ok(RuntimeValue::from_bigint(BigInt::from_i64(*a).mul(b)))
781 }
782 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a * b)),
783 (RuntimeValue::Int(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(*a as f64 * b)),
784 (RuntimeValue::Float(a), RuntimeValue::Int(b)) => Ok(RuntimeValue::Float(a * *b as f64)),
785 (RuntimeValue::BigInt(a), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(a.to_f64() * b)),
786 (RuntimeValue::Float(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::Float(a * b.to_f64())),
787 (RuntimeValue::Rational(a), RuntimeValue::Rational(b)) => {
788 Ok(RuntimeValue::from_rational(a.mul(b)))
789 }
790 (RuntimeValue::Rational(_), RuntimeValue::Int(_))
791 | (RuntimeValue::Int(_), RuntimeValue::Rational(_))
792 | (RuntimeValue::Rational(_), RuntimeValue::BigInt(_))
793 | (RuntimeValue::BigInt(_), RuntimeValue::Rational(_)) => {
794 Ok(RuntimeValue::from_rational(rat_of(&left).unwrap().mul(&rat_of(&right).unwrap())))
795 }
796 (RuntimeValue::Rational(r), RuntimeValue::Float(b)) => Ok(RuntimeValue::Float(r.to_f64() * b)),
797 (RuntimeValue::Float(a), RuntimeValue::Rational(r)) => Ok(RuntimeValue::Float(a * r.to_f64())),
798 (RuntimeValue::List(items), RuntimeValue::Int(n))
803 | (RuntimeValue::Int(n), RuntimeValue::List(items)) => {
804 use crate::interpreter::ListRepr;
805 let src = items.borrow().to_values();
806 let count = (*n).max(0) as usize;
807 let mut out = Vec::with_capacity(src.len() * count);
808 for _ in 0..count {
809 out.extend(src.iter().map(|v| v.deep_clone()));
810 }
811 Ok(RuntimeValue::List(Rc::new(std::cell::RefCell::new(ListRepr::from_values(out)))))
812 }
813 _ => Err(format!(
814 "Cannot multiply {} and {}",
815 left.type_name(),
816 right.type_name()
817 )),
818 }
819}
820
821pub fn divide(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
822 if let Some(r) = quantity_binop(&left, &right, '/') {
823 return r;
824 }
825 if let Some(r) = money_binop(&left, &right, '/') {
826 return r;
827 }
828 if matches!(left, RuntimeValue::Modular(_)) || matches!(right, RuntimeValue::Modular(_)) {
831 return match (&left, &right) {
832 (RuntimeValue::Modular(a), RuntimeValue::Modular(b)) => {
833 if a.modulus() != b.modulus() {
834 Err("cannot divide values in different modular rings".to_string())
835 } else {
836 a.div(b).map(|r| RuntimeValue::Modular(Rc::new(r))).ok_or_else(|| {
837 "modular divisor has no inverse (not coprime to the modulus)".to_string()
838 })
839 }
840 }
841 _ => Err(format!("Cannot divide {} by {}", left.type_name(), right.type_name())),
842 };
843 }
844 if matches!(left, RuntimeValue::Complex(_)) || matches!(right, RuntimeValue::Complex(_)) {
846 return match (complex_of(&left), complex_of(&right)) {
847 (Some(a), Some(b)) => a
848 .div(&b)
849 .map(|c| RuntimeValue::Complex(Rc::new(c)))
850 .ok_or_else(|| "Division by zero".to_string()),
851 _ => Err(format!("Cannot divide {} by {}", left.type_name(), right.type_name())),
852 };
853 }
854 if matches!(left, RuntimeValue::Decimal(_)) || matches!(right, RuntimeValue::Decimal(_)) {
857 if let (Some(a), Some(b)) = (rat_of(&left), rat_of(&right)) {
858 return a
859 .div(&b)
860 .map(RuntimeValue::from_rational)
861 .ok_or_else(|| "Division by zero".to_string());
862 }
863 if let (Some(a), Some(b)) = (num_f64(&left), num_f64(&right)) {
864 if b == 0.0 {
865 return Err("Division by zero".to_string());
866 }
867 return Ok(RuntimeValue::Float(a / b));
868 }
869 }
870 match (&left, &right) {
871 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => {
872 if *b == 0 {
873 return Err("Division by zero".to_string());
874 }
875 Ok(match a.checked_div(*b) {
878 Some(q) => RuntimeValue::Int(q),
879 None => RuntimeValue::from_bigint(int_div(*a, *b).0),
880 })
881 }
882 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => {
883 Ok(RuntimeValue::from_bigint(a.div_rem(b).expect("BigInt divisor is never zero").0))
884 }
885 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
886 if *b == 0 {
887 return Err("Division by zero".to_string());
888 }
889 Ok(RuntimeValue::from_bigint(a.div_rem(&BigInt::from_i64(*b)).unwrap().0))
890 }
891 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
892 Ok(RuntimeValue::from_bigint(BigInt::from_i64(*a).div_rem(b).unwrap().0))
893 }
894 (RuntimeValue::Float(a), RuntimeValue::Float(b)) => {
895 if *b == 0.0 {
896 return Err("Division by zero".to_string());
897 }
898 Ok(RuntimeValue::Float(a / b))
899 }
900 (RuntimeValue::Int(a), RuntimeValue::Float(b)) => {
901 if *b == 0.0 {
902 return Err("Division by zero".to_string());
903 }
904 Ok(RuntimeValue::Float(*a as f64 / b))
905 }
906 (RuntimeValue::Float(a), RuntimeValue::Int(b)) => {
907 if *b == 0 {
908 return Err("Division by zero".to_string());
909 }
910 Ok(RuntimeValue::Float(a / *b as f64))
911 }
912 (RuntimeValue::BigInt(a), RuntimeValue::Float(b)) => {
913 if *b == 0.0 {
914 return Err("Division by zero".to_string());
915 }
916 Ok(RuntimeValue::Float(a.to_f64() / b))
917 }
918 (RuntimeValue::Float(a), RuntimeValue::BigInt(b)) => Ok(RuntimeValue::Float(a / b.to_f64())),
919 (RuntimeValue::Rational(_), RuntimeValue::Rational(_))
922 | (RuntimeValue::Rational(_), RuntimeValue::Int(_))
923 | (RuntimeValue::Int(_), RuntimeValue::Rational(_))
924 | (RuntimeValue::Rational(_), RuntimeValue::BigInt(_))
925 | (RuntimeValue::BigInt(_), RuntimeValue::Rational(_)) => rat_of(&left)
926 .unwrap()
927 .div(&rat_of(&right).unwrap())
928 .map(RuntimeValue::from_rational)
929 .ok_or_else(|| "Division by zero".to_string()),
930 (RuntimeValue::Rational(r), RuntimeValue::Float(b)) => {
931 if *b == 0.0 {
932 return Err("Division by zero".to_string());
933 }
934 Ok(RuntimeValue::Float(r.to_f64() / b))
935 }
936 (RuntimeValue::Float(a), RuntimeValue::Rational(r)) => Ok(RuntimeValue::Float(a / r.to_f64())),
937 _ => Err(format!(
938 "Cannot divide {} by {}",
939 left.type_name(),
940 right.type_name()
941 )),
942 }
943}
944
945fn int_div(a: i64, b: i64) -> (BigInt, BigInt) {
948 BigInt::from_i64(a).div_rem(&BigInt::from_i64(b)).expect("nonzero divisor")
949}
950
951pub fn floor_divide(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
957 if matches!(left, RuntimeValue::Float(_)) || matches!(right, RuntimeValue::Float(_)) {
959 if let (Some(a), Some(b)) = (num_f64(&left), num_f64(&right)) {
960 if b == 0.0 {
961 return Err("Division by zero".to_string());
962 }
963 return Ok(RuntimeValue::Float((a / b).floor()));
964 }
965 }
966 if let (Some(a), Some(b)) = (rat_of(&left), rat_of(&right)) {
969 return a
970 .div(&b)
971 .map(|q| RuntimeValue::from_bigint(q.floor()))
972 .ok_or_else(|| "Division by zero".to_string());
973 }
974 Err(format!(
975 "Cannot floor-divide {} by {}",
976 left.type_name(),
977 right.type_name()
978 ))
979}
980
981pub fn exact_divide(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
987 match (&left, &right) {
988 (a, b) if rat_of(a).is_some() && rat_of(b).is_some() => rat_of(&left)
991 .unwrap()
992 .div(&rat_of(&right).unwrap())
993 .map(RuntimeValue::from_rational)
994 .ok_or_else(|| "Division by zero".to_string()),
995 (RuntimeValue::Float(_), _) | (_, RuntimeValue::Float(_)) => divide(left, right),
998 _ => Err(format!("Cannot divide {} by {}", left.type_name(), right.type_name())),
999 }
1000}
1001
1002pub fn modulo(left: RuntimeValue, right: RuntimeValue) -> Result<RuntimeValue, String> {
1003 match (&left, &right) {
1004 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => {
1005 if *b == 0 {
1006 return Err("Modulo by zero".to_string());
1007 }
1008 Ok(RuntimeValue::Int(a.checked_rem(*b).unwrap_or(0)))
1010 }
1011 (RuntimeValue::BigInt(a), RuntimeValue::BigInt(b)) => {
1012 Ok(RuntimeValue::from_bigint(a.div_rem(b).expect("BigInt divisor is never zero").1))
1013 }
1014 (RuntimeValue::BigInt(a), RuntimeValue::Int(b)) => {
1015 if *b == 0 {
1016 return Err("Modulo by zero".to_string());
1017 }
1018 Ok(RuntimeValue::from_bigint(a.div_rem(&BigInt::from_i64(*b)).unwrap().1))
1019 }
1020 (RuntimeValue::Int(a), RuntimeValue::BigInt(b)) => {
1021 Ok(RuntimeValue::from_bigint(BigInt::from_i64(*a).div_rem(b).unwrap().1))
1022 }
1023 _ => Err(format!(
1024 "Cannot compute modulo of {} and {}",
1025 left.type_name(),
1026 right.type_name()
1027 )),
1028 }
1029}
1030
1031pub fn crdt_counter_bump(
1036 current: RuntimeValue,
1037 amount: i64,
1038 field_name: &str,
1039) -> Result<RuntimeValue, String> {
1040 match current {
1041 RuntimeValue::Int(n) => Ok(RuntimeValue::Int(n.wrapping_add(amount))),
1042 RuntimeValue::Nothing => Ok(RuntimeValue::Int(amount)),
1043 _ => Err(format!("Field '{}' is not a counter", field_name)),
1044 }
1045}
1046
1047pub const GCOUNTER_TAG: &str = "__GCounter";
1056
1057pub fn gcounter_value(v: &RuntimeValue) -> Option<i64> {
1060 match v {
1061 RuntimeValue::Struct(s) if s.type_name == GCOUNTER_TAG => Some(
1062 s.fields
1063 .values()
1064 .map(|c| if let RuntimeValue::Int(n) = c { *n } else { 0 })
1065 .fold(0i64, i64::wrapping_add),
1066 ),
1067 _ => None,
1068 }
1069}
1070
1071pub fn crdt_merge_field(current: &RuntimeValue, incoming: RuntimeValue) -> RuntimeValue {
1072 match (current, &incoming) {
1073 (RuntimeValue::Struct(a), RuntimeValue::Struct(b))
1078 if a.type_name == GCOUNTER_TAG && b.type_name == GCOUNTER_TAG =>
1079 {
1080 let mut fields = a.fields.clone();
1081 for (replica, count) in &b.fields {
1082 let next = match (fields.get(replica), count) {
1083 (Some(RuntimeValue::Int(x)), RuntimeValue::Int(y)) => RuntimeValue::Int((*x).max(*y)),
1084 _ => count.clone(),
1085 };
1086 fields.insert(replica.clone(), next);
1087 }
1088 RuntimeValue::Struct(Box::new(crate::interpreter::StructValue {
1089 type_name: GCOUNTER_TAG.to_string(),
1090 fields,
1091 }))
1092 }
1093 (RuntimeValue::Int(a), RuntimeValue::Int(b)) => RuntimeValue::Int(a.wrapping_add(*b)),
1094 (RuntimeValue::Set(_), RuntimeValue::Set(_)) => {
1095 crate::semantics::collections::union(current, &incoming).unwrap_or(incoming)
1096 }
1097 (RuntimeValue::Map(a), RuntimeValue::Map(b)) => {
1102 let mut out = a.borrow().clone();
1103 for (k, v) in b.borrow().iter() {
1104 let merged = match out.get(k) {
1105 Some(cur) => crdt_merge_field(cur, v.clone()),
1106 None => v.clone(),
1107 };
1108 out.insert(k.clone(), merged);
1109 }
1110 RuntimeValue::Map(std::rc::Rc::new(std::cell::RefCell::new(out)))
1111 }
1112 (RuntimeValue::Crdt(a), RuntimeValue::Crdt(b)) => {
1117 let mut merged = a.borrow().clone();
1118 let _ = merged.merge(&b.borrow());
1119 RuntimeValue::Crdt(std::rc::Rc::new(std::cell::RefCell::new(merged)))
1120 }
1121 _ => incoming,
1122 }
1123}
1124
1125fn field_to_json(value: &RuntimeValue) -> Option<serde_json::Value> {
1129 use serde_json::{json, Value};
1130 Some(match value {
1131 RuntimeValue::Int(n) => json!(n),
1132 RuntimeValue::Bool(b) => json!(b),
1133 RuntimeValue::Float(f) => json!(f),
1134 RuntimeValue::Text(s) => json!(s.as_str()),
1135 RuntimeValue::Nothing => Value::Null,
1136 RuntimeValue::Set(items) => {
1137 Value::Array(items.borrow().iter().filter_map(field_to_json).collect())
1138 }
1139 RuntimeValue::Map(m) => {
1142 let pairs: Vec<Value> = m
1143 .borrow()
1144 .iter()
1145 .filter_map(|(k, v)| Some(Value::Array(vec![field_to_json(k)?, field_to_json(v)?])))
1146 .collect();
1147 json!({ "__map": pairs })
1148 }
1149 _ => return None,
1150 })
1151}
1152
1153fn field_from_json(value: &serde_json::Value) -> RuntimeValue {
1156 use serde_json::Value;
1157 match value {
1158 Value::Bool(b) => RuntimeValue::Bool(*b),
1159 Value::String(s) => RuntimeValue::Text(std::rc::Rc::new(s.clone())),
1160 Value::Number(n) => match n.as_i64() {
1161 Some(i) => RuntimeValue::Int(i),
1162 None => RuntimeValue::Float(n.as_f64().unwrap_or(0.0)),
1163 },
1164 Value::Array(items) => RuntimeValue::Set(std::rc::Rc::new(std::cell::RefCell::new(
1165 items.iter().map(field_from_json).collect(),
1166 ))),
1167 Value::Object(o) if o.contains_key("__map") => {
1169 let mut map = crate::interpreter::MapStorage::default();
1170 if let Some(Value::Array(pairs)) = o.get("__map") {
1171 for p in pairs {
1172 if let Value::Array(kv) = p {
1173 if kv.len() == 2 {
1174 map.insert(field_from_json(&kv[0]), field_from_json(&kv[1]));
1175 }
1176 }
1177 }
1178 }
1179 RuntimeValue::Map(std::rc::Rc::new(std::cell::RefCell::new(map)))
1180 }
1181 _ => RuntimeValue::Nothing,
1182 }
1183}
1184
1185pub fn crdt_to_wire(value: &RuntimeValue) -> Option<Vec<u8>> {
1190 use serde_json::{Map, Value};
1191 let mut map = Map::new();
1192 match value {
1193 RuntimeValue::Nothing => return None,
1194 RuntimeValue::Struct(s) => {
1195 for (k, v) in &s.fields {
1196 if let Some(j) = field_to_json(v) {
1197 map.insert(k.clone(), j);
1198 }
1199 }
1200 }
1201 other => match field_to_json(other) {
1203 Some(j) => {
1204 map.insert(String::new(), j);
1205 }
1206 None => return None,
1207 },
1208 }
1209 serde_json::to_vec(&Value::Object(map)).ok()
1210}
1211
1212pub fn crdt_merge_wire(local: RuntimeValue, bytes: &[u8]) -> RuntimeValue {
1217 let Ok(serde_json::Value::Object(map)) = serde_json::from_slice::<serde_json::Value>(bytes)
1218 else {
1219 return local;
1220 };
1221 match local {
1222 RuntimeValue::Struct(mut s) => {
1223 for (k, v) in map {
1224 let incoming = field_from_json(&v);
1225 let current = s.fields.get(&k).cloned().unwrap_or(RuntimeValue::Nothing);
1226 s.fields.insert(k, crdt_merge_field(¤t, incoming));
1227 }
1228 RuntimeValue::Struct(s)
1229 }
1230 other => match map.get("") {
1231 Some(v) => crdt_merge_field(&other, field_from_json(v)),
1232 None => other,
1233 },
1234 }
1235}
1236
1237pub fn not_value(val: RuntimeValue) -> Result<RuntimeValue, String> {
1239 Ok(RuntimeValue::Bool(!val.is_truthy()))
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use super::*;
1245 use crate::interpreter::StructValue;
1246 use std::collections::HashMap;
1247
1248 #[test]
1249 fn money_arithmetic_and_comparison_are_exact_and_currency_safe() {
1250 use crate::ast::stmt::BinaryOpKind;
1251 use crate::semantics::builtins::{call_builtin, BuiltinId};
1252 use crate::semantics::compare::compare;
1253 let money = |s: &str, code: &str| {
1254 call_builtin(
1255 BuiltinId::Money,
1256 vec![
1257 RuntimeValue::Decimal(Rc::new(logicaffeine_base::Decimal::parse(s).unwrap())),
1258 RuntimeValue::Text(Rc::new(code.to_string())),
1259 ],
1260 )
1261 .unwrap()
1262 };
1263 let show = |v: &RuntimeValue| v.to_display_string();
1264
1265 assert_eq!(show(&money("19.99", "USD")), "19.99 USD");
1267 assert_eq!(show(&add(money("0.10", "USD"), money("0.20", "USD")).unwrap()), "0.30 USD");
1269 assert_eq!(show(&add(money("19.99", "USD"), money("5.00", "USD")).unwrap()), "24.99 USD");
1270 assert_eq!(show(&subtract(money("24.99", "USD"), money("5.00", "USD")).unwrap()), "19.99 USD");
1271 assert!(add(money("5.00", "USD"), money("1.00", "EUR")).is_err());
1273 assert!(subtract(money("5.00", "USD"), money("1.00", "EUR")).is_err());
1274 assert_eq!(show(&multiply(money("19.99", "USD"), RuntimeValue::Int(3)).unwrap()), "59.97 USD");
1276 assert_eq!(show(&multiply(RuntimeValue::Int(3), money("19.99", "USD")).unwrap()), "59.97 USD");
1277 assert_eq!(show(÷(money("10.00", "USD"), RuntimeValue::Int(4)).unwrap()), "2.50 USD");
1278 assert!(matches!(
1280 divide(money("30.00", "USD"), money("10.00", "USD")).unwrap(),
1281 RuntimeValue::Int(3) | RuntimeValue::Rational(_)
1282 ));
1283 assert!(multiply(money("2.00", "USD"), money("2.00", "USD")).is_err());
1284 assert_eq!(
1286 compare(BinaryOpKind::Gt, &money("5.00", "USD"), &money("1.00", "USD")).unwrap(),
1287 RuntimeValue::Bool(true)
1288 );
1289 assert!(compare(BinaryOpKind::Lt, &money("5.00", "USD"), &money("1.00", "EUR")).is_err());
1290 assert!(call_builtin(
1292 BuiltinId::Money,
1293 vec![RuntimeValue::Float(1.5), RuntimeValue::Text(Rc::new("USD".to_string()))]
1294 )
1295 .is_err());
1296 assert!(call_builtin(
1297 BuiltinId::Money,
1298 vec![RuntimeValue::Int(5), RuntimeValue::Text(Rc::new("XYZ".to_string()))]
1299 )
1300 .is_err());
1301 }
1302
1303 #[test]
1304 fn crdt_wire_int_into_nothing_takes_value() {
1305 let bytes = crdt_to_wire(&RuntimeValue::Int(7)).unwrap();
1306 assert!(matches!(crdt_merge_wire(RuntimeValue::Nothing, &bytes), RuntimeValue::Int(7)));
1307 }
1308
1309 #[test]
1310 fn crdt_wire_int_merge_adds() {
1311 let bytes = crdt_to_wire(&RuntimeValue::Int(5)).unwrap();
1312 assert!(matches!(crdt_merge_wire(RuntimeValue::Int(3), &bytes), RuntimeValue::Int(8)));
1313 }
1314
1315 #[test]
1316 fn crdt_wire_struct_merges_fieldwise() {
1317 let mut fields = HashMap::new();
1318 fields.insert("a".to_string(), RuntimeValue::Int(2));
1319 fields.insert("b".to_string(), RuntimeValue::Int(4));
1320 let incoming = RuntimeValue::Struct(Box::new(StructValue {
1321 type_name: "Counter".into(),
1322 fields,
1323 }));
1324 let bytes = crdt_to_wire(&incoming).unwrap();
1325
1326 let mut local_fields = HashMap::new();
1327 local_fields.insert("a".to_string(), RuntimeValue::Int(1)); let local = RuntimeValue::Struct(Box::new(StructValue {
1329 type_name: "Counter".into(),
1330 fields: local_fields,
1331 }));
1332 match crdt_merge_wire(local, &bytes) {
1333 RuntimeValue::Struct(s) => {
1334 assert!(matches!(s.fields.get("a"), Some(RuntimeValue::Int(3))), "1 + 2");
1335 assert!(matches!(s.fields.get("b"), Some(RuntimeValue::Int(4))), "0 + 4");
1336 }
1337 other => panic!("expected a struct, got {other:?}"),
1338 }
1339 }
1340
1341 #[test]
1342 fn crdt_wire_nothing_has_nothing_to_publish() {
1343 assert!(crdt_to_wire(&RuntimeValue::Nothing).is_none());
1344 }
1345
1346 #[test]
1347 fn crdt_wire_bool_lww_takes_incoming() {
1348 let bytes = crdt_to_wire(&RuntimeValue::Bool(true)).unwrap();
1349 assert!(matches!(
1350 crdt_merge_wire(RuntimeValue::Bool(false), &bytes),
1351 RuntimeValue::Bool(true)
1352 ));
1353 }
1354
1355 #[test]
1356 fn crdt_wire_text_lww() {
1357 let bytes = crdt_to_wire(&RuntimeValue::Text(std::rc::Rc::new("hi".into()))).unwrap();
1358 match crdt_merge_wire(RuntimeValue::Nothing, &bytes) {
1359 RuntimeValue::Text(s) => assert_eq!(&*s, "hi"),
1360 other => panic!("expected Text, got {other:?}"),
1361 }
1362 }
1363
1364 #[test]
1365 fn crdt_wire_float_roundtrips() {
1366 let bytes = crdt_to_wire(&RuntimeValue::Float(2.5)).unwrap();
1367 match crdt_merge_wire(RuntimeValue::Nothing, &bytes) {
1368 RuntimeValue::Float(f) => assert_eq!(f, 2.5),
1369 other => panic!("expected Float, got {other:?}"),
1370 }
1371 }
1372
1373 fn set_of(ns: &[i64]) -> RuntimeValue {
1374 RuntimeValue::Set(std::rc::Rc::new(std::cell::RefCell::new(
1375 ns.iter().map(|n| RuntimeValue::Int(*n)).collect(),
1376 )))
1377 }
1378
1379 #[test]
1380 fn crdt_wire_set_unions() {
1381 let bytes = crdt_to_wire(&set_of(&[2, 3])).unwrap();
1382 match crdt_merge_wire(set_of(&[1, 2]), &bytes) {
1383 RuntimeValue::Set(items) => {
1384 let v = items.borrow();
1385 assert_eq!(v.len(), 3, "{{1,2}} ∪ {{2,3}} = {{1,2,3}}");
1386 for n in [1, 2, 3] {
1387 assert!(
1388 v.iter().any(|x| matches!(x, RuntimeValue::Int(m) if *m == n)),
1389 "missing {n}"
1390 );
1391 }
1392 }
1393 other => panic!("expected Set, got {other:?}"),
1394 }
1395 }
1396
1397 #[test]
1398 fn crdt_wire_struct_mixed_types_merge_each_by_its_rule() {
1399 let mut fields = HashMap::new();
1400 fields.insert("hits".to_string(), RuntimeValue::Int(3));
1401 fields.insert("title".to_string(), RuntimeValue::Text(std::rc::Rc::new("v2".into())));
1402 fields.insert("tags".to_string(), set_of(&[9]));
1403 let incoming = RuntimeValue::Struct(Box::new(StructValue {
1404 type_name: "Page".into(),
1405 fields,
1406 }));
1407 let bytes = crdt_to_wire(&incoming).unwrap();
1408
1409 let mut local = HashMap::new();
1410 local.insert("hits".to_string(), RuntimeValue::Int(1));
1411 local.insert("title".to_string(), RuntimeValue::Text(std::rc::Rc::new("v1".into())));
1412 local.insert("tags".to_string(), set_of(&[7]));
1413 let local = RuntimeValue::Struct(Box::new(StructValue {
1414 type_name: "Page".into(),
1415 fields: local,
1416 }));
1417
1418 match crdt_merge_wire(local, &bytes) {
1419 RuntimeValue::Struct(s) => {
1420 assert!(matches!(s.fields.get("hits"), Some(RuntimeValue::Int(4))), "counter 1+3");
1421 assert!(
1422 matches!(s.fields.get("title"), Some(RuntimeValue::Text(t)) if &***t == "v2"),
1423 "LWW register"
1424 );
1425 match s.fields.get("tags") {
1426 Some(RuntimeValue::Set(items)) => {
1427 assert_eq!(items.borrow().len(), 2, "set union {{7}} ∪ {{9}}")
1428 }
1429 other => panic!("tags not a set: {other:?}"),
1430 }
1431 }
1432 other => panic!("expected struct, got {other:?}"),
1433 }
1434 }
1435
1436 #[test]
1437 fn error_messages_are_canonical() {
1438 let e = add(RuntimeValue::Bool(true), RuntimeValue::Nothing).unwrap_err();
1439 assert_eq!(e, "Cannot add Bool and Nothing");
1440 let e = subtract(RuntimeValue::Bool(true), RuntimeValue::Nothing).unwrap_err();
1441 assert_eq!(e, "Cannot subtract Nothing from Bool");
1442 let e = multiply(RuntimeValue::Bool(true), RuntimeValue::Nothing).unwrap_err();
1443 assert_eq!(e, "Cannot multiply Bool and Nothing");
1444 let e = divide(RuntimeValue::Int(1), RuntimeValue::Int(0)).unwrap_err();
1445 assert_eq!(e, "Division by zero");
1446 let e = divide(RuntimeValue::Float(1.0), RuntimeValue::Float(0.0)).unwrap_err();
1447 assert_eq!(e, "Division by zero");
1448 let e = modulo(RuntimeValue::Int(1), RuntimeValue::Int(0)).unwrap_err();
1449 assert_eq!(e, "Modulo by zero");
1450 let r = not_value(RuntimeValue::Nothing).unwrap();
1452 assert!(matches!(r, RuntimeValue::Bool(true)));
1453 }
1454
1455 #[test]
1456 fn text_add_stringifies_either_side() {
1457 let r = add(
1458 RuntimeValue::Text(Rc::new("n=".to_string())),
1459 RuntimeValue::Int(4),
1460 )
1461 .unwrap();
1462 assert!(matches!(&r, RuntimeValue::Text(s) if **s == "n=4"));
1463 let r = add(
1464 RuntimeValue::Int(4),
1465 RuntimeValue::Text(Rc::new("!".to_string())),
1466 )
1467 .unwrap();
1468 assert!(matches!(&r, RuntimeValue::Text(s) if **s == "4!"));
1469 }
1470
1471 #[test]
1472 fn date_plus_span_is_calendar_aware() {
1473 let r = add(
1475 RuntimeValue::Date(19753),
1476 RuntimeValue::Span { months: 1, days: 0 },
1477 )
1478 .unwrap();
1479 assert!(matches!(r, RuntimeValue::Date(19782)));
1480 let r = subtract(
1482 RuntimeValue::Date(19782),
1483 RuntimeValue::Span { months: 0, days: 1 },
1484 )
1485 .unwrap();
1486 assert!(matches!(r, RuntimeValue::Date(19781)));
1487 }
1488
1489 #[test]
1490 fn decimal_arithmetic_stays_exact_and_promotes_correctly() {
1491 let d = |s: &str| RuntimeValue::Decimal(Rc::new(Decimal::parse(s).unwrap()));
1492 assert_eq!(add(d("19.99"), d("0.01")).unwrap().to_display_string(), "20.00");
1494 assert_eq!(subtract(d("20.00"), d("0.01")).unwrap().to_display_string(), "19.99");
1495 assert_eq!(multiply(d("1.1"), d("1.1")).unwrap().to_display_string(), "1.21");
1496 assert_eq!(add(d("0.1"), d("0.2")).unwrap().to_display_string(), "0.3");
1497 assert_eq!(multiply(d("19.99"), RuntimeValue::Int(3)).unwrap().to_display_string(), "59.97");
1499 assert!(matches!(add(d("1.50"), RuntimeValue::Int(1)).unwrap(), RuntimeValue::Decimal(_)));
1500 let q = divide(d("1"), d("3")).unwrap();
1502 assert!(matches!(q, RuntimeValue::Rational(_)));
1503 assert_eq!(q.to_display_string(), "1/3");
1504 let third = RuntimeValue::from_rational(Rational::from_ratio_i64(1, 3).unwrap());
1506 assert!(matches!(add(d("0.5"), third).unwrap(), RuntimeValue::Rational(_)));
1507 assert!(matches!(add(d("0.5"), RuntimeValue::Float(0.25)).unwrap(), RuntimeValue::Float(_)));
1508 assert!(matches!(
1510 compare(BinaryOpKind::Gt, &d("19.99"), &RuntimeValue::Int(10)).unwrap(),
1511 RuntimeValue::Bool(true)
1512 ));
1513 }
1514
1515 #[test]
1516 fn complex_arithmetic_is_exact_closed_and_unordered() {
1517 let c = |re: i64, im: i64| {
1518 RuntimeValue::Complex(Rc::new(Complex::new(Rational::from_i64(re), Rational::from_i64(im))))
1519 };
1520 let cq = |rn: i64, rd: i64, in_: i64, id: i64| {
1521 RuntimeValue::Complex(Rc::new(Complex::new(
1522 Rational::from_ratio_i64(rn, rd).unwrap(),
1523 Rational::from_ratio_i64(in_, id).unwrap(),
1524 )))
1525 };
1526 let i = c(0, 1);
1527 assert_eq!(multiply(i.clone(), i.clone()).unwrap(), c(-1, 0));
1529 assert_eq!(add(c(2, 3), c(1, -1)).unwrap(), c(3, 2));
1531 assert_eq!(subtract(c(5, 2), c(1, 7)).unwrap(), c(4, -5));
1532 assert_eq!(multiply(c(1, 1), c(1, -1)).unwrap(), c(2, 0));
1534 assert_eq!(multiply(c(2, 3), c(4, 5)).unwrap(), c(-7, 22));
1536 assert_eq!(divide(c(3, 4), c(1, 2)).unwrap(), cq(11, 5, -2, 5));
1538 assert_eq!(divide(c(2, 3), c(2, 3)).unwrap(), c(1, 0));
1539 assert_eq!(add(c(2, 3), RuntimeValue::Int(5)).unwrap(), c(7, 3));
1541 assert_eq!(multiply(i.clone(), RuntimeValue::Int(3)).unwrap(), c(0, 3)); let half = RuntimeValue::from_rational(Rational::from_ratio_i64(1, 2).unwrap());
1543 assert_eq!(add(c(1, 0), half).unwrap(), cq(3, 2, 0, 1));
1544 assert!(matches!(
1546 binary_op(BinaryOpKind::Eq, c(3, 4), c(3, 4)).unwrap(),
1547 RuntimeValue::Bool(true)
1548 ));
1549 assert!(matches!(
1550 binary_op(BinaryOpKind::Eq, c(3, 4), c(3, -4)).unwrap(),
1551 RuntimeValue::Bool(false)
1552 ));
1553 assert!(add(c(1, 1), RuntimeValue::Float(0.5)).is_err());
1555 assert!(multiply(RuntimeValue::Float(2.0), i.clone()).is_err());
1556 assert!(divide(c(1, 1), c(0, 0)).is_err());
1558 for op in [BinaryOpKind::Lt, BinaryOpKind::Gt, BinaryOpKind::LtEq, BinaryOpKind::GtEq] {
1560 assert!(compare(op, &c(1, 1), &c(2, 2)).is_err(), "complex is unordered under {op:?}");
1561 }
1562 assert_eq!(c(3, 4).to_display_string(), "3+4i");
1564 assert_eq!(c(0, 1).to_display_string(), "i");
1565 assert_eq!(c(0, -1).to_display_string(), "-i");
1566 assert_eq!(c(-1, 0).to_display_string(), "-1");
1567 }
1568
1569 fn kind(v: &RuntimeValue) -> &'static str {
1571 match v {
1572 RuntimeValue::Int(_) => "Int",
1573 RuntimeValue::BigInt(_) => "BigInt",
1574 RuntimeValue::Rational(_) => "Rational",
1575 RuntimeValue::Decimal(_) => "Decimal",
1576 RuntimeValue::Complex(_) => "Complex",
1577 RuntimeValue::Modular(_) => "Modular",
1578 RuntimeValue::Float(_) => "Float",
1579 _ => "other", }
1581 }
1582
1583 #[test]
1589 fn numeric_tower_cross_type_promotion_gauntlet() {
1590 let int = || RuntimeValue::Int(2);
1591 let rat = || RuntimeValue::from_rational(Rational::from_ratio_i64(1, 3).unwrap());
1592 let dec = || RuntimeValue::Decimal(Rc::new(Decimal::parse("0.5").unwrap()));
1593 let cpx = || RuntimeValue::Complex(Rc::new(Complex::new(Rational::from_i64(2), Rational::from_i64(3))));
1594 let flt = || RuntimeValue::Float(2.0);
1595
1596 let g = |a: RuntimeValue, b: RuntimeValue| add(a, b);
1598 assert_eq!(kind(&g(int(), int()).unwrap()), "Int");
1599 assert_eq!(g(int(), rat()).unwrap().to_display_string(), "7/3"); assert_eq!(kind(&g(int(), rat()).unwrap()), "Rational");
1601 assert_eq!(g(int(), dec()).unwrap().to_display_string(), "2.5"); assert_eq!(kind(&g(int(), dec()).unwrap()), "Decimal");
1603 assert_eq!(g(int(), cpx()).unwrap().to_display_string(), "4+3i"); assert_eq!(kind(&g(int(), cpx()).unwrap()), "Complex");
1605 assert_eq!(kind(&g(int(), flt()).unwrap()), "Float");
1606 assert_eq!(g(rat(), dec()).unwrap().to_display_string(), "5/6"); assert_eq!(kind(&g(rat(), dec()).unwrap()), "Rational"); assert_eq!(g(rat(), cpx()).unwrap().to_display_string(), "7/3+3i"); assert_eq!(kind(&g(rat(), cpx()).unwrap()), "Complex");
1610 assert_eq!(kind(&g(rat(), flt()).unwrap()), "Float");
1611 assert_eq!(g(dec(), dec()).unwrap().to_display_string(), "1.0"); assert_eq!(kind(&g(dec(), dec()).unwrap()), "Decimal");
1613 assert_eq!(g(dec(), cpx()).unwrap().to_display_string(), "5/2+3i"); assert_eq!(kind(&g(dec(), cpx()).unwrap()), "Complex");
1615 assert_eq!(kind(&g(dec(), flt()).unwrap()), "Float");
1616 assert_eq!(g(cpx(), cpx()).unwrap().to_display_string(), "4+6i");
1617 assert_eq!(kind(&g(flt(), flt()).unwrap()), "Float");
1618 assert!(g(cpx(), flt()).is_err(), "Complex + Float must be a typed error");
1620 assert!(g(flt(), cpx()).is_err(), "Float + Complex must be a typed error");
1621
1622 for a in [int(), rat(), dec(), cpx(), flt()] {
1624 for b in [int(), rat(), dec(), cpx(), flt()] {
1625 match (add(a.clone(), b.clone()), add(b.clone(), a.clone())) {
1626 (Ok(ab), Ok(ba)) => assert_eq!(kind(&ab), kind(&ba), "promotion commutes: {} {}", kind(&a), kind(&b)),
1627 (Err(_), Err(_)) => {} other => panic!("promotion asymmetry for {} and {}: {other:?}", kind(&a), kind(&b)),
1629 }
1630 }
1631 }
1632
1633 assert_eq!(divide(dec(), int()).unwrap().to_display_string(), "1/4"); assert_eq!(kind(÷(dec(), int()).unwrap()), "Rational");
1636 assert_eq!(divide(dec(), dec()).unwrap().to_display_string(), "1"); assert_eq!(divide(cpx(), int()).unwrap().to_display_string(), "1+3/2i"); assert_eq!(kind(÷(cpx(), int()).unwrap()), "Complex");
1639 assert_eq!(divide(cpx(), cpx()).unwrap().to_display_string(), "1"); assert_eq!(divide(rat(), int()).unwrap().to_display_string(), "1/6"); assert!(divide(dec(), RuntimeValue::Int(0)).is_err());
1643 assert!(divide(cpx(), RuntimeValue::from_rational(Rational::zero())).is_err());
1644 }
1645
1646 #[test]
1647 fn modular_arithmetic_wraps_and_requires_a_shared_ring() {
1648 let m = |v: i64, n: i64| {
1649 RuntimeValue::Modular(Rc::new(Modular::from_i64(v, n).unwrap()))
1650 };
1651 assert_eq!(add(m(5, 7), m(4, 7)).unwrap(), m(2, 7)); assert_eq!(subtract(m(3, 7), m(5, 7)).unwrap(), m(5, 7)); assert_eq!(multiply(m(4, 7), m(5, 7)).unwrap(), m(6, 7)); assert_eq!(divide(m(1, 7), m(3, 7)).unwrap(), m(5, 7));
1657 assert!(divide(m(1, 4), m(2, 4)).is_err());
1659 assert!(add(m(3, 7), m(3, 5)).is_err());
1661 assert!(multiply(m(3, 7), m(3, 5)).is_err());
1662 assert!(divide(m(3, 7), m(3, 5)).is_err());
1663 assert!(add(m(3, 7), RuntimeValue::Int(5)).is_err());
1665 assert!(matches!(binary_op(BinaryOpKind::Eq, m(3, 7), m(10, 7)).unwrap(), RuntimeValue::Bool(true)));
1667 assert!(matches!(binary_op(BinaryOpKind::Eq, m(3, 7), m(3, 5)).unwrap(), RuntimeValue::Bool(false)));
1668 assert!(compare(BinaryOpKind::Lt, &m(1, 7), &m(2, 7)).is_err());
1669 assert_eq!(m(3, 7).to_display_string(), "3 (mod 7)");
1670 }
1671
1672 #[test]
1673 fn int_arithmetic_is_exact_in_every_build_profile() {
1674 let r = add(RuntimeValue::Int(i64::MAX), RuntimeValue::Int(1)).unwrap();
1676 assert_eq!(r.to_display_string(), "9223372036854775808"); let r = subtract(RuntimeValue::Int(i64::MIN), RuntimeValue::Int(1)).unwrap();
1678 assert_eq!(r.to_display_string(), "-9223372036854775809");
1679 let r = multiply(RuntimeValue::Int(i64::MAX), RuntimeValue::Int(2)).unwrap();
1680 assert_eq!(r.to_display_string(), "18446744073709551614");
1681 let r = divide(RuntimeValue::Int(i64::MIN), RuntimeValue::Int(-1)).unwrap();
1683 assert_eq!(r.to_display_string(), "9223372036854775808");
1684 let r = modulo(RuntimeValue::Int(i64::MIN), RuntimeValue::Int(-1)).unwrap();
1685 assert!(matches!(r, RuntimeValue::Int(0)));
1686 let r = add(RuntimeValue::Duration(i64::MAX), RuntimeValue::Duration(1)).unwrap();
1689 assert!(matches!(r, RuntimeValue::Duration(i64::MIN)));
1690 let r = subtract(RuntimeValue::Duration(i64::MIN), RuntimeValue::Duration(1)).unwrap();
1691 assert!(matches!(r, RuntimeValue::Duration(i64::MAX)));
1692 }
1693
1694 #[test]
1695 fn shifts_mask_their_count_modulo_64() {
1696 let r = binary_op(BinaryOpKind::Shl, RuntimeValue::Int(1), RuntimeValue::Int(64)).unwrap();
1699 assert!(matches!(r, RuntimeValue::Int(1)));
1700 let r = binary_op(BinaryOpKind::Shl, RuntimeValue::Int(1), RuntimeValue::Int(63)).unwrap();
1701 assert!(matches!(r, RuntimeValue::Int(i64::MIN)));
1702 let r = binary_op(BinaryOpKind::Shl, RuntimeValue::Int(1), RuntimeValue::Int(-1)).unwrap();
1704 assert!(matches!(r, RuntimeValue::Int(i64::MIN)));
1705 let r = binary_op(BinaryOpKind::Shr, RuntimeValue::Int(i64::MIN), RuntimeValue::Int(63)).unwrap();
1706 assert!(matches!(r, RuntimeValue::Int(-1)));
1707 let r = binary_op(BinaryOpKind::Shr, RuntimeValue::Int(8), RuntimeValue::Int(64)).unwrap();
1708 assert!(matches!(r, RuntimeValue::Int(8)));
1709 }
1710
1711 #[test]
1712 fn crdt_counter_bump_wraps() {
1713 let r = crdt_counter_bump(RuntimeValue::Int(i64::MAX), 1, "n").unwrap();
1714 assert!(matches!(r, RuntimeValue::Int(i64::MIN)));
1715 let r = crdt_counter_bump(RuntimeValue::Nothing, 5, "n").unwrap();
1716 assert!(matches!(r, RuntimeValue::Int(5)));
1717 let e = crdt_counter_bump(RuntimeValue::Bool(true), 1, "score").unwrap_err();
1718 assert_eq!(e, "Field 'score' is not a counter");
1719 }
1720
1721 #[test]
1722 fn eager_and_or_are_logical_truthiness_to_bool() {
1723 let r = binary_op(BinaryOpKind::And, RuntimeValue::Int(6), RuntimeValue::Int(3)).unwrap();
1726 assert!(matches!(r, RuntimeValue::Bool(true)));
1727 let r = binary_op(BinaryOpKind::Or, RuntimeValue::Int(0), RuntimeValue::Int(7)).unwrap();
1728 assert!(matches!(r, RuntimeValue::Bool(true)));
1729 let r = binary_op(BinaryOpKind::And, RuntimeValue::Int(1), RuntimeValue::Bool(false)).unwrap();
1730 assert!(matches!(r, RuntimeValue::Bool(false)));
1731 let r = binary_op(BinaryOpKind::Or, RuntimeValue::Bool(false), RuntimeValue::Bool(true)).unwrap();
1732 assert!(matches!(r, RuntimeValue::Bool(true)));
1733 let r = binary_op(BinaryOpKind::BitAnd, RuntimeValue::Int(6), RuntimeValue::Int(3)).unwrap();
1734 assert!(matches!(r, RuntimeValue::Int(2)));
1735 let r = binary_op(BinaryOpKind::BitOr, RuntimeValue::Int(6), RuntimeValue::Int(3)).unwrap();
1736 assert!(matches!(r, RuntimeValue::Int(7)));
1737 }
1738}
1739
1740#[cfg(test)]
1745mod bigint_exact_arithmetic {
1746 use super::*;
1747 use logicaffeine_base::BigInt;
1748
1749 fn int(n: i64) -> RuntimeValue {
1750 RuntimeValue::Int(n)
1751 }
1752 fn two_pow_63() -> RuntimeValue {
1755 add(int(i64::MAX), int(1)).unwrap()
1756 }
1757 fn disp(v: &RuntimeValue) -> String {
1758 v.to_display_string()
1759 }
1760 fn is_big(v: &RuntimeValue) -> bool {
1761 matches!(v, RuntimeValue::BigInt(_))
1762 }
1763
1764 #[test]
1765 fn add_at_the_boundary_promotes_not_wraps() {
1766 let r = add(int(i64::MAX), int(1)).unwrap();
1768 assert!(is_big(&r), "i64::MAX + 1 must be a BigInt, not a wrapped Int");
1770 assert_eq!(disp(&r), "9223372036854775808");
1771 assert_ne!(r, int(i64::MIN), "the JSON/2's-complement footgun must be gone");
1772 }
1773
1774 #[test]
1775 fn subtract_below_the_boundary_promotes() {
1776 let r = subtract(int(i64::MIN), int(1)).unwrap();
1777 assert!(is_big(&r));
1778 assert_eq!(disp(&r), "-9223372036854775809");
1779 }
1780
1781 #[test]
1782 fn multiply_overflow_promotes() {
1783 let r = multiply(int(i64::MAX), int(2)).unwrap();
1784 assert_eq!(disp(&r), "18446744073709551614");
1785 assert!(is_big(&r));
1786 }
1787
1788 #[test]
1789 fn results_that_fit_downsize_back_to_int() {
1790 let big = two_pow_63(); let back = subtract(big.clone(), int(1)).unwrap();
1794 assert_eq!(back, int(i64::MAX), "must downsize to Int");
1795 assert!(!is_big(&back));
1796 let zero = add(big.clone(), subtract(int(0), big).unwrap()).unwrap();
1798 assert_eq!(zero, int(0));
1799 }
1800
1801 #[test]
1802 fn every_operand_mix_is_handled_for_add_sub_mul() {
1803 let big = two_pow_63(); assert_eq!(disp(&add(int(1), big.clone()).unwrap()), "9223372036854775809");
1806 assert_eq!(disp(&add(big.clone(), int(1)).unwrap()), "9223372036854775809");
1807 assert_eq!(disp(&add(big.clone(), big.clone()).unwrap()), "18446744073709551616"); assert_eq!(disp(&subtract(big.clone(), big.clone()).unwrap()), "0");
1810 assert_eq!(disp(&multiply(big.clone(), int(2)).unwrap()), "18446744073709551616");
1812 assert_eq!(disp(&multiply(int(2), big.clone()).unwrap()), "18446744073709551616");
1813 }
1814
1815 #[test]
1816 fn divide_and_modulo_cover_the_overflow_and_big_operands() {
1817 let q = divide(int(i64::MIN), int(-1)).unwrap();
1819 assert_eq!(disp(&q), "9223372036854775808");
1820 assert_eq!(modulo(int(i64::MIN), int(-1)).unwrap(), int(0));
1821 let big = two_pow_63(); assert_eq!(divide(big.clone(), int(2)).unwrap(), int(4611686018427387904));
1824 assert_eq!(modulo(big.clone(), int(2)).unwrap(), int(0));
1825 assert_eq!(divide(int(5), big.clone()).unwrap(), int(0));
1827 assert_eq!(modulo(int(5), big).unwrap(), int(5));
1828 assert!(divide(int(1), int(0)).is_err());
1830 assert!(modulo(int(1), int(0)).is_err());
1831 }
1832
1833 #[test]
1834 fn mixing_a_bigint_with_a_float_yields_a_float() {
1835 let big = two_pow_63();
1836 match add(big.clone(), RuntimeValue::Float(1.0)).unwrap() {
1837 RuntimeValue::Float(f) => assert!((f - 9223372036854775809.0).abs() < 1e9),
1838 other => panic!("expected Float, got {}", other.type_name()),
1839 }
1840 assert!(matches!(multiply(RuntimeValue::Float(2.0), big).unwrap(), RuntimeValue::Float(_)));
1841 }
1842
1843 #[test]
1844 fn comparison_orders_across_the_narrow_wide_boundary() {
1845 let big = two_pow_63(); let neg_big = subtract(int(i64::MIN), int(1)).unwrap(); let lt = |a: &RuntimeValue, b: &RuntimeValue| {
1848 matches!(
1849 super::super::compare::compare(BinaryOpKind::Lt, a, b).unwrap(),
1850 RuntimeValue::Bool(true)
1851 )
1852 };
1853 assert!(lt(&int(i64::MAX), &big), "i64::MAX < 2^63");
1854 assert!(lt(&neg_big, &int(i64::MIN)), "-(2^63+1) < i64::MIN");
1855 assert!(lt(&neg_big, &big), "huge negative < huge positive");
1856 assert!(!lt(&big.clone(), &big));
1858 }
1859
1860 #[test]
1861 fn equality_and_hashing_are_consistent_for_bigints() {
1862 use std::collections::HashSet;
1863 let a = two_pow_63();
1864 let b = add(int(1), int(i64::MAX)).unwrap(); assert_eq!(a, b, "equal BigInts compare equal");
1866 assert_ne!(a, int(0), "a BigInt is never equal to an Int");
1867 let mut set = HashSet::new();
1869 set.insert(a);
1870 set.insert(b);
1871 assert_eq!(set.len(), 1, "equal BigInts must hash-collapse");
1872 }
1873
1874 #[test]
1875 fn dense_differential_against_i128_through_the_arith_layer() {
1876 let xs: [i64; 9] = [0, 1, -1, 7, -7, i32::MAX as i64, i32::MIN as i64, i64::MAX, i64::MIN];
1879 for &x in &xs {
1880 for &y in &xs {
1881 assert_eq!(disp(&add(int(x), int(y)).unwrap()), (x as i128 + y as i128).to_string(), "{x}+{y}");
1882 assert_eq!(disp(&subtract(int(x), int(y)).unwrap()), (x as i128 - y as i128).to_string(), "{x}-{y}");
1883 assert_eq!(disp(&multiply(int(x), int(y)).unwrap()), (x as i128 * y as i128).to_string(), "{x}*{y}");
1884 if y != 0 {
1885 assert_eq!(disp(÷(int(x), int(y)).unwrap()), (x as i128 / y as i128).to_string(), "{x}/{y}");
1886 assert_eq!(disp(&modulo(int(x), int(y)).unwrap()), (x as i128 % y as i128).to_string(), "{x}%{y}");
1887 }
1888 }
1889 }
1890 }
1891
1892 #[test]
1893 fn promoted_values_round_trip_through_a_negation_chain() {
1894 let mut v = int(i64::MAX - 1);
1897 for _ in 0..4 {
1898 v = add(v, int(1)).unwrap();
1899 }
1900 assert_eq!(disp(&v), "9223372036854775810"); assert!(is_big(&v));
1902 for _ in 0..4 {
1903 v = subtract(v, int(1)).unwrap();
1904 }
1905 assert_eq!(v, int(i64::MAX - 1), "back to the exact narrow value");
1906 assert!(!is_big(&v));
1907 }
1908
1909 #[test]
1910 fn fuzz_promotion_layer_matches_i128_and_obeys_algebra() {
1911 let mut state = 0x5DEE_CE66_1357_2468u64;
1913 let mut next = || {
1914 state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
1915 let mut z = state;
1916 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1917 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1918 z ^ (z >> 31)
1919 };
1920 for _ in 0..4000 {
1921 let (x, y) = (next() as i64, next() as i64);
1922 assert_eq!(disp(&add(int(x), int(y)).unwrap()), (x as i128 + y as i128).to_string(), "{x}+{y}");
1924 assert_eq!(disp(&subtract(int(x), int(y)).unwrap()), (x as i128 - y as i128).to_string(), "{x}-{y}");
1925 assert_eq!(disp(&multiply(int(x), int(y)).unwrap()), (x as i128 * y as i128).to_string(), "{x}*{y}");
1926 if y != 0 {
1927 assert_eq!(disp(÷(int(x), int(y)).unwrap()), (x as i128 / y as i128).to_string(), "{x}/{y}");
1928 assert_eq!(disp(&modulo(int(x), int(y)).unwrap()), (x as i128 % y as i128).to_string(), "{x}%{y}");
1929 }
1930 assert_eq!(add(int(x), int(y)).unwrap(), add(int(y), int(x)).unwrap());
1932 assert_eq!(multiply(int(x), int(y)).unwrap(), multiply(int(y), int(x)).unwrap());
1933 let exact = x as i128 + y as i128;
1935 let fits = (i64::MIN as i128..=i64::MAX as i128).contains(&exact);
1936 assert_eq!(is_big(&add(int(x), int(y)).unwrap()), !fits, "BigInt iff out of i64 range for {x}+{y}");
1937 }
1938 }
1939
1940 #[test]
1941 fn from_bigint_constructor_maintains_the_downsizing_invariant() {
1942 assert_eq!(RuntimeValue::from_bigint(BigInt::from_i64(42)), int(42));
1944 assert!(!is_big(&RuntimeValue::from_bigint(BigInt::from_i64(i64::MIN))));
1945 assert!(is_big(&RuntimeValue::from_bigint(BigInt::from_i64(i64::MAX).add(&BigInt::from_i64(1)))));
1947 }
1948}
1949
1950#[cfg(test)]
1956mod rational_exact_arithmetic {
1957 use super::*;
1958 use crate::interpreter::StructValue;
1959 use logicaffeine_base::Rational;
1960 use std::collections::HashMap;
1961
1962 fn rat(n: i64, d: i64) -> RuntimeValue {
1963 RuntimeValue::from_rational(Rational::from_ratio_i64(n, d).unwrap())
1964 }
1965 fn int(n: i64) -> RuntimeValue {
1966 RuntimeValue::Int(n)
1967 }
1968 fn is_rat(v: &RuntimeValue) -> bool {
1969 matches!(v, RuntimeValue::Rational(_))
1970 }
1971
1972 #[test]
1973 fn from_rational_downsizes_whole_values_to_int() {
1974 assert_eq!(rat(6, 2), int(3));
1976 assert!(!is_rat(&rat(6, 2)));
1977 assert_eq!(rat(0, 5), int(0));
1978 assert!(is_rat(&rat(7, 2)));
1980 assert_eq!(rat(7, 2).to_display_string(), "7/2");
1981 assert_eq!(rat(7, 2).type_name(), "Rational");
1982 }
1983
1984 #[test]
1985 fn rational_arithmetic_is_exact_and_downsizes() {
1986 assert_eq!(add(rat(1, 2), rat(1, 2)).unwrap(), int(1));
1988 assert_eq!(add(rat(1, 3), rat(1, 6)).unwrap().to_display_string(), "1/2");
1990 assert_eq!(subtract(rat(1, 2), rat(1, 3)).unwrap().to_display_string(), "1/6");
1992 assert_eq!(multiply(rat(2, 3), rat(3, 4)).unwrap().to_display_string(), "1/2");
1993 assert_eq!(divide(rat(1, 2), rat(3, 4)).unwrap().to_display_string(), "2/3");
1994 }
1995
1996 #[test]
1997 fn rational_mixes_with_int_and_bigint_exactly() {
1998 assert_eq!(add(rat(1, 2), int(1)).unwrap().to_display_string(), "3/2");
2000 assert_eq!(multiply(int(3), rat(1, 2)).unwrap().to_display_string(), "3/2");
2001 assert_eq!(divide(rat(3, 2), int(3)).unwrap(), rat(1, 2));
2002 assert_eq!(add(rat(1, 3), rat(2, 3)).unwrap(), int(1));
2004 }
2005
2006 #[test]
2007 fn rational_with_a_float_operand_becomes_float() {
2008 assert!(matches!(add(rat(1, 2), RuntimeValue::Float(0.5)).unwrap(), RuntimeValue::Float(f) if (f - 1.0).abs() < 1e-12));
2010 assert!(matches!(divide(rat(1, 2), RuntimeValue::Float(2.0)).unwrap(), RuntimeValue::Float(f) if (f - 0.25).abs() < 1e-12));
2011 }
2012
2013 #[test]
2014 fn rational_equality_and_ordering_are_exact() {
2015 assert!(!values_equal(&rat(7, 2), &int(3)));
2017 assert!(values_equal(&rat(7, 2), &rat(7, 2)));
2018 let lt = |a, b| matches!(compare(BinaryOpKind::Lt, &a, &b).unwrap(), RuntimeValue::Bool(true));
2020 assert!(lt(rat(1, 3), rat(1, 2)));
2021 assert!(lt(rat(1, 2), rat(2, 3)));
2022 assert!(lt(rat(1, 2), int(1)));
2024 assert!(lt(int(0), rat(1, 2)));
2025 }
2026
2027 #[test]
2028 fn dividing_a_rational_by_zero_errors() {
2029 assert_eq!(divide(rat(1, 2), int(0)).unwrap_err(), "Division by zero");
2030 assert_eq!(divide(rat(1, 2), rat(0, 1)).unwrap_err(), "Division by zero");
2031 }
2032
2033 struct Rng(u64);
2040 impl Rng {
2041 fn new(seed: u64) -> Self {
2042 Rng(seed)
2043 }
2044 fn next(&mut self) -> u64 {
2045 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
2046 let mut z = self.0;
2047 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
2048 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
2049 z ^ (z >> 31)
2050 }
2051 fn upto(&mut self, n: u64) -> u64 {
2052 self.next() % n.max(1)
2053 }
2054 }
2055
2056 fn crdt_eq(a: &RuntimeValue, b: &RuntimeValue) -> bool {
2059 match (a, b) {
2060 (RuntimeValue::Int(x), RuntimeValue::Int(y)) => x == y,
2061 (RuntimeValue::Text(x), RuntimeValue::Text(y)) => x == y,
2062 (RuntimeValue::Bool(x), RuntimeValue::Bool(y)) => x == y,
2063 (RuntimeValue::Nothing, RuntimeValue::Nothing) => true,
2064 (RuntimeValue::Set(x), RuntimeValue::Set(y)) => {
2065 let (xb, yb) = (x.borrow(), y.borrow());
2066 xb.len() == yb.len() && xb.iter().all(|e| yb.iter().any(|f| crdt_eq(e, f)))
2067 }
2068 (RuntimeValue::Map(x), RuntimeValue::Map(y)) => {
2069 let (xb, yb) = (x.borrow(), y.borrow());
2070 xb.len() == yb.len() && xb.iter().all(|(k, v)| yb.get(k).map_or(false, |w| crdt_eq(v, w)))
2071 }
2072 (RuntimeValue::Struct(x), RuntimeValue::Struct(y)) => {
2073 x.type_name == y.type_name
2074 && x.fields.len() == y.fields.len()
2075 && x.fields.iter().all(|(k, v)| y.fields.get(k).map_or(false, |w| crdt_eq(v, w)))
2076 }
2077 _ => false,
2078 }
2079 }
2080
2081 fn rand_gcounter(rng: &mut Rng) -> RuntimeValue {
2082 let mut fields: HashMap<String, RuntimeValue> = HashMap::new();
2083 for _ in 0..rng.upto(4) {
2084 let r = format!("r{}", rng.upto(4));
2085 let c = rng.upto(20) as i64;
2086 let cur = if let Some(RuntimeValue::Int(x)) = fields.get(&r) { *x } else { 0 };
2087 fields.insert(r, RuntimeValue::Int(cur.max(c)));
2088 }
2089 RuntimeValue::Struct(Box::new(StructValue { type_name: GCOUNTER_TAG.to_string(), fields }))
2090 }
2091
2092 fn gcounter(pairs: &[(&str, i64)]) -> RuntimeValue {
2093 let mut fields = HashMap::new();
2094 for (r, c) in pairs {
2095 fields.insert(r.to_string(), RuntimeValue::Int(*c));
2096 }
2097 RuntimeValue::Struct(Box::new(StructValue { type_name: GCOUNTER_TAG.to_string(), fields }))
2098 }
2099
2100 #[test]
2101 fn gcounter_is_gossip_safe_max_per_replica_and_idempotent_under_redelivery() {
2102 let a = gcounter(&[("alice", 3), ("bob", 1)]);
2105 let b = gcounter(&[("alice", 2), ("carol", 5)]);
2106 let ab = crdt_merge_field(&a, b.clone());
2107 assert_eq!(gcounter_value(&ab), Some(3 + 1 + 5), "MAX per replica → alice=3, bob=1, carol=5");
2108 let abb = crdt_merge_field(&ab, b.clone());
2109 assert_eq!(gcounter_value(&abb), Some(9), "REDELIVERY is a no-op — never double-counts");
2110 assert!(crdt_eq(&ab, &abb), "the state is unchanged under redelivery (idempotent)");
2111 }
2112
2113 #[test]
2114 fn gcounter_merge_obeys_the_crdt_laws() {
2115 assert_crdt_laws(&mut Rng::new(0x06C0_0117), rand_gcounter, "g-counter");
2116 }
2117
2118 #[test]
2119 fn gcounter_replicas_converge_under_any_order_and_duplication() {
2120 let mut rng = Rng::new(0x06C0_F1A6);
2123 for _ in 0..500 {
2124 let states: Vec<RuntimeValue> = (0..4).map(|_| rand_gcounter(&mut rng)).collect();
2125 let mut truth = states[0].clone();
2126 for s in &states[1..] {
2127 truth = crdt_merge_field(&truth, s.clone());
2128 }
2129 let mut deliveries: Vec<usize> = (0..states.len()).chain(0..states.len()).collect();
2130 for i in (1..deliveries.len()).rev() {
2131 let j = rng.upto((i + 1) as u64) as usize;
2132 deliveries.swap(i, j);
2133 }
2134 let mut replica = states[rng.upto(states.len() as u64) as usize].clone();
2135 for &d in &deliveries {
2136 replica = crdt_merge_field(&replica, states[d].clone());
2137 }
2138 assert!(crdt_eq(&replica, &truth), "g-counter replicas converge despite order/duplication");
2139 assert_eq!(gcounter_value(&replica), gcounter_value(&truth), "and agree on the total");
2140 }
2141 }
2142
2143 fn set_of(items: &[i64]) -> RuntimeValue {
2144 RuntimeValue::Set(std::rc::Rc::new(std::cell::RefCell::new(
2145 items.iter().map(|&i| RuntimeValue::Int(i)).collect(),
2146 )))
2147 }
2148
2149 fn rand_set(rng: &mut Rng) -> RuntimeValue {
2150 let mut s = set_of(&[]);
2151 for _ in 0..rng.upto(6) {
2152 s = crate::semantics::collections::union(&s, &set_of(&[rng.upto(8) as i64])).unwrap();
2153 }
2154 s
2155 }
2156
2157 fn rand_map_of_sets(rng: &mut Rng) -> RuntimeValue {
2158 let mut m = crate::interpreter::MapStorage::default();
2159 for _ in 0..rng.upto(5) {
2160 m.insert(RuntimeValue::Int(rng.upto(5) as i64), rand_set(rng));
2161 }
2162 RuntimeValue::Map(std::rc::Rc::new(std::cell::RefCell::new(m)))
2163 }
2164
2165 fn assert_crdt_laws(rng: &mut Rng, gen: impl Fn(&mut Rng) -> RuntimeValue, what: &str) {
2166 for _ in 0..2000 {
2167 let (a, b, c) = (gen(rng), gen(rng), gen(rng));
2168 assert!(
2169 crdt_eq(&crdt_merge_field(&a, b.clone()), &crdt_merge_field(&b, a.clone())),
2170 "{what} merge must be COMMUTATIVE"
2171 );
2172 assert!(crdt_eq(&crdt_merge_field(&a, a.clone()), &a), "{what} merge must be IDEMPOTENT");
2173 let ab = crdt_merge_field(&a, b.clone());
2174 assert!(crdt_eq(&crdt_merge_field(&ab, b.clone()), &ab), "{what}: re-merging a seen value is a no-op");
2175 let l = crdt_merge_field(&crdt_merge_field(&a, b.clone()), c.clone());
2176 let r = crdt_merge_field(&a, crdt_merge_field(&b, c.clone()));
2177 assert!(crdt_eq(&l, &r), "{what} merge must be ASSOCIATIVE");
2178 }
2179 }
2180
2181 #[test]
2182 fn crdt_set_merge_obeys_the_crdt_laws() {
2183 assert_crdt_laws(&mut Rng::new(0x00C0_FFEE), rand_set, "set");
2184 }
2185
2186 #[test]
2187 fn crdt_map_of_sets_merge_obeys_the_crdt_laws() {
2188 assert_crdt_laws(&mut Rng::new(0x0000_BEEF), rand_map_of_sets, "map-of-sets");
2190 }
2191
2192 #[test]
2193 fn crdt_replicas_converge_under_any_order_and_duplication() {
2194 let mut rng = Rng::new(0x0000_5EED);
2197 for _ in 0..500 {
2198 let states: Vec<RuntimeValue> = (0..4).map(|_| rand_map_of_sets(&mut rng)).collect();
2199 let mut truth = states[0].clone();
2200 for s in &states[1..] {
2201 truth = crdt_merge_field(&truth, s.clone());
2202 }
2203 let mut deliveries: Vec<usize> = (0..states.len()).chain(0..states.len()).collect();
2205 for i in (1..deliveries.len()).rev() {
2206 let j = rng.upto((i + 1) as u64) as usize;
2207 deliveries.swap(i, j);
2208 }
2209 let mut replica = states[rng.upto(states.len() as u64) as usize].clone();
2210 for &d in &deliveries {
2211 replica = crdt_merge_field(&replica, states[d].clone());
2212 }
2213 assert!(crdt_eq(&replica, &truth), "every replica must converge to the LUB despite order/duplication");
2214 }
2215 }
2216
2217 #[test]
2218 fn crdt_map_round_trips_and_merges_through_the_wire() {
2219 let mut rng = Rng::new(0x0057_17E0);
2222 for _ in 0..400 {
2223 let local = rand_map_of_sets(&mut rng);
2224 let incoming = rand_map_of_sets(&mut rng);
2225 let expected = crdt_merge_field(&local, incoming.clone());
2226 let bytes = crdt_to_wire(&incoming).expect("a map publishes to the wire");
2227 let merged = crdt_merge_wire(local.clone(), &bytes);
2228 assert!(crdt_eq(&merged, &expected), "wire merge must equal the in-memory merge");
2229 }
2230 }
2231}
2232
2233#[cfg(test)]
2234mod word_tests {
2235 use super::*;
2236 use logicaffeine_base::{Word32, Word64, WordVal};
2237
2238 fn w32(n: u32) -> RuntimeValue {
2239 RuntimeValue::Word(WordVal::W32(Word32(n)))
2240 }
2241 fn w64(n: u64) -> RuntimeValue {
2242 RuntimeValue::Word(WordVal::W64(Word64(n)))
2243 }
2244
2245 #[test]
2246 fn word_arithmetic_wraps_through_binary_op() {
2247 assert_eq!(binary_op(BinaryOpKind::Add, w32(0xFFFF_FFFF), w32(1)).unwrap(), w32(0));
2249 assert_eq!(binary_op(BinaryOpKind::Subtract, w32(0), w32(1)).unwrap(), w32(0xFFFF_FFFF));
2250 assert_eq!(binary_op(BinaryOpKind::Multiply, w32(0x1000_0000), w32(0x10)).unwrap(), w32(0));
2251 assert_eq!(binary_op(BinaryOpKind::BitXor, w32(0xFF00), w32(0x0FF0)).unwrap(), w32(0xF0F0));
2252 assert_eq!(binary_op(BinaryOpKind::And, w32(0xFF00), w32(0x0FF0)).unwrap(), w32(0x0F00));
2253 assert_eq!(binary_op(BinaryOpKind::Or, w32(0xFF00), w32(0x0FF0)).unwrap(), w32(0xFFF0));
2254 }
2255
2256 #[test]
2257 fn word_shift_takes_an_integer_count() {
2258 assert_eq!(binary_op(BinaryOpKind::Shl, w32(1), RuntimeValue::Int(8)).unwrap(), w32(0x100));
2259 assert_eq!(binary_op(BinaryOpKind::Shr, w32(0x100), RuntimeValue::Int(8)).unwrap(), w32(1));
2260 }
2261
2262 #[test]
2263 fn word_equality_and_width_mismatch_is_typed() {
2264 assert!(values_equal(&w32(5), &w32(5)));
2265 assert!(!values_equal(&w32(5), &w64(5)), "different widths are never equal");
2266 assert!(
2267 binary_op(BinaryOpKind::Add, w32(1), w64(1)).is_err(),
2268 "Word32 + Word64 must error, not silently coerce"
2269 );
2270 }
2271}