use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::sync::Mutex as AsyncMutex;
use tokio::task::JoinHandle;
use crate::error::RuntimeError;
#[derive(Debug)]
pub struct ChannelHandle {
pub sender: UnboundedSender<Value>,
pub receiver: AsyncMutex<UnboundedReceiver<Value>>,
}
impl ChannelHandle {
#[must_use]
pub fn new() -> Arc<Self> {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
Arc::new(ChannelHandle {
sender: tx,
receiver: AsyncMutex::new(rx),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct OrdF64(pub f64);
impl PartialEq for OrdF64 {
fn eq(&self, other: &Self) -> bool {
self.0.total_cmp(&other.0) == std::cmp::Ordering::Equal
}
}
impl Eq for OrdF64 {}
impl PartialOrd for OrdF64 {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrdF64 {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.total_cmp(&other.0)
}
}
impl Hash for OrdF64 {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl fmt::Display for OrdF64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<f64> for OrdF64 {
fn from(v: f64) -> Self {
OrdF64(v)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BockString(String);
impl BockString {
#[must_use]
pub fn new(s: impl Into<String>) -> Self {
BockString(s.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for BockString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for BockString {
fn from(s: String) -> Self {
BockString(s)
}
}
impl From<&str> for BockString {
fn from(s: &str) -> Self {
BockString(s.to_owned())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RecordValue {
pub type_name: String,
pub fields: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EnumValue {
pub type_name: String,
pub variant: String,
pub payload: Option<Box<Value>>,
}
static NEXT_FN_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Clone)]
pub struct FnValue {
pub id: u64,
pub name: Option<String>,
}
impl FnValue {
#[must_use]
pub fn new_anonymous() -> Self {
FnValue {
id: NEXT_FN_ID.fetch_add(1, AtomicOrdering::Relaxed),
name: None,
}
}
#[must_use]
pub fn new_named(name: impl Into<String>) -> Self {
FnValue {
id: NEXT_FN_ID.fetch_add(1, AtomicOrdering::Relaxed),
name: Some(name.into()),
}
}
}
impl PartialEq for FnValue {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for FnValue {}
impl Hash for FnValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl fmt::Display for FnValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.name {
Some(n) => write!(f, "<fn {n}>"),
None => write!(f, "<fn #{}>", self.id),
}
}
}
static NEXT_ITER_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug)]
pub enum IteratorKind {
List { items: Vec<Value>, pos: usize },
Range {
current: i64,
end: i64,
inclusive: bool,
step: i64,
},
Set { items: Vec<Value>, pos: usize },
MapEntries {
items: Vec<(Value, Value)>,
pos: usize,
},
Map {
source: Arc<Mutex<IteratorKind>>,
func: FnValue,
},
Filter {
source: Arc<Mutex<IteratorKind>>,
pred: FnValue,
},
Take {
source: Arc<Mutex<IteratorKind>>,
remaining: usize,
},
Skip {
source: Arc<Mutex<IteratorKind>>,
to_skip: usize,
skipped: bool,
},
Enumerate {
source: Arc<Mutex<IteratorKind>>,
index: usize,
},
Zip {
a: Arc<Mutex<IteratorKind>>,
b: Arc<Mutex<IteratorKind>>,
},
Chain {
a: Arc<Mutex<IteratorKind>>,
b: Arc<Mutex<IteratorKind>>,
first_done: bool,
},
}
#[derive(Debug)]
pub enum IteratorNext {
Some(Value),
Done,
NeedsMapCallback { value: Value, func: FnValue },
NeedsFilterCallback { value: Value, func: FnValue },
}
impl IteratorKind {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> IteratorNext {
match self {
IteratorKind::List { items, pos } => {
if *pos < items.len() {
let val = items[*pos].clone();
*pos += 1;
IteratorNext::Some(val)
} else {
IteratorNext::Done
}
}
IteratorKind::Range {
current,
end,
inclusive,
step,
} => {
let in_bounds = if *step > 0 {
if *inclusive {
*current <= *end
} else {
*current < *end
}
} else if *step < 0 {
if *inclusive {
*current >= *end
} else {
*current > *end
}
} else {
false };
if in_bounds {
let val = *current;
*current += *step;
IteratorNext::Some(Value::Int(val))
} else {
IteratorNext::Done
}
}
IteratorKind::Set { items, pos } => {
if *pos < items.len() {
let val = items[*pos].clone();
*pos += 1;
IteratorNext::Some(val)
} else {
IteratorNext::Done
}
}
IteratorKind::MapEntries { items, pos } => {
if *pos < items.len() {
let (k, v) = items[*pos].clone();
*pos += 1;
IteratorNext::Some(Value::Tuple(vec![k, v]))
} else {
IteratorNext::Done
}
}
IteratorKind::Map { source, func } => {
let mut src = source.lock().unwrap();
match src.next() {
IteratorNext::Some(val) => IteratorNext::NeedsMapCallback {
value: val,
func: func.clone(),
},
IteratorNext::Done => IteratorNext::Done,
other => other,
}
}
IteratorKind::Filter { source, pred } => {
let mut src = source.lock().unwrap();
match src.next() {
IteratorNext::Some(val) => IteratorNext::NeedsFilterCallback {
value: val,
func: pred.clone(),
},
IteratorNext::Done => IteratorNext::Done,
other => other,
}
}
IteratorKind::Take { source, remaining } => {
if *remaining == 0 {
return IteratorNext::Done;
}
*remaining -= 1;
source.lock().unwrap().next()
}
IteratorKind::Skip {
source,
to_skip,
skipped,
} => {
if !*skipped {
*skipped = true;
let mut src = source.lock().unwrap();
for _ in 0..*to_skip {
match src.next() {
IteratorNext::Done => return IteratorNext::Done,
IteratorNext::Some(_) => {}
other => return other,
}
}
}
source.lock().unwrap().next()
}
IteratorKind::Enumerate { source, index } => {
let mut src = source.lock().unwrap();
match src.next() {
IteratorNext::Some(val) => {
let idx = *index;
*index += 1;
IteratorNext::Some(Value::Tuple(vec![Value::Int(idx as i64), val]))
}
other => other,
}
}
IteratorKind::Zip { a, b } => {
let next_a = a.lock().unwrap().next();
match next_a {
IteratorNext::Some(va) => {
let next_b = b.lock().unwrap().next();
match next_b {
IteratorNext::Some(vb) => {
IteratorNext::Some(Value::Tuple(vec![va, vb]))
}
IteratorNext::Done => IteratorNext::Done,
other => other,
}
}
IteratorNext::Done => IteratorNext::Done,
other => other,
}
}
IteratorKind::Chain { a, b, first_done } => {
if !*first_done {
let result = a.lock().unwrap().next();
match result {
IteratorNext::Done => {
*first_done = true;
b.lock().unwrap().next()
}
other => other,
}
} else {
b.lock().unwrap().next()
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct IteratorValue {
pub id: u64,
pub kind: Arc<Mutex<IteratorKind>>,
}
impl IteratorValue {
#[must_use]
pub fn new(kind: IteratorKind) -> Self {
IteratorValue {
id: NEXT_ITER_ID.fetch_add(1, AtomicOrdering::Relaxed),
kind: Arc::new(Mutex::new(kind)),
}
}
}
impl PartialEq for IteratorValue {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for IteratorValue {}
impl Hash for IteratorValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl fmt::Display for IteratorValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<iterator #{}>", self.id)
}
}
#[derive(Debug, Clone)]
pub enum Value {
Int(i64),
Float(OrdF64),
Bool(bool),
String(BockString),
Char(char),
Void,
List(Vec<Value>),
Map(BTreeMap<Value, Value>),
Set(BTreeSet<Value>),
Tuple(Vec<Value>),
Record(RecordValue),
Enum(EnumValue),
Function(FnValue),
Optional(Option<Box<Value>>),
Result(std::result::Result<Box<Value>, Box<Value>>),
Range {
start: i64,
end: i64,
inclusive: bool,
step: i64,
},
Iterator(IteratorValue),
StringBuilder(Arc<Mutex<String>>),
Future(FutureHandle),
Duration(i64),
Instant(std::time::Instant),
Channel(Arc<ChannelHandle>),
}
pub type FutureHandle = Arc<Mutex<Option<JoinHandle<Result<Value, RuntimeError>>>>>;
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Char(a), Value::Char(b)) => a == b,
(Value::Void, Value::Void) => true,
(Value::List(a), Value::List(b)) => a == b,
(Value::Map(a), Value::Map(b)) => a == b,
(Value::Set(a), Value::Set(b)) => a == b,
(Value::Tuple(a), Value::Tuple(b)) => a == b,
(Value::Record(a), Value::Record(b)) => a == b,
(Value::Enum(a), Value::Enum(b)) => a == b,
(Value::Function(a), Value::Function(b)) => a == b,
(Value::Optional(a), Value::Optional(b)) => a == b,
(Value::Result(a), Value::Result(b)) => match (a, b) {
(Ok(av), Ok(bv)) => av == bv,
(Err(ae), Err(be)) => ae == be,
_ => false,
},
(
Value::Range {
start: s1,
end: e1,
inclusive: i1,
step: st1,
},
Value::Range {
start: s2,
end: e2,
inclusive: i2,
step: st2,
},
) => s1 == s2 && e1 == e2 && i1 == i2 && st1 == st2,
(Value::Iterator(a), Value::Iterator(b)) => a == b,
(Value::StringBuilder(a), Value::StringBuilder(b)) => Arc::ptr_eq(a, b),
(Value::Future(a), Value::Future(b)) => Arc::ptr_eq(a, b),
(Value::Duration(a), Value::Duration(b)) => a == b,
(Value::Instant(a), Value::Instant(b)) => a == b,
(Value::Channel(a), Value::Channel(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl Eq for Value {}
fn variant_ord(v: &Value) -> u8 {
match v {
Value::Void => 0,
Value::Bool(_) => 1,
Value::Int(_) => 2,
Value::Float(_) => 3,
Value::Char(_) => 4,
Value::String(_) => 5,
Value::Tuple(_) => 6,
Value::List(_) => 7,
Value::Set(_) => 8,
Value::Map(_) => 9,
Value::Record(_) => 10,
Value::Enum(_) => 11,
Value::Optional(_) => 12,
Value::Result(_) => 13,
Value::Function(_) => 14,
Value::Range { .. } => 15,
Value::Iterator(_) => 16,
Value::StringBuilder(_) => 17,
Value::Future(_) => 18,
Value::Duration(_) => 19,
Value::Instant(_) => 20,
Value::Channel(_) => 21,
}
}
impl Ord for Value {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (self, other) {
(Value::Void, Value::Void) => Ordering::Equal,
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Int(a), Value::Int(b)) => a.cmp(b),
(Value::Float(a), Value::Float(b)) => a.cmp(b),
(Value::Char(a), Value::Char(b)) => a.cmp(b),
(Value::String(a), Value::String(b)) => a.cmp(b),
(Value::Tuple(a), Value::Tuple(b)) => a.cmp(b),
(Value::List(a), Value::List(b)) => a.cmp(b),
(Value::Set(a), Value::Set(b)) => a.cmp(b),
(Value::Map(a), Value::Map(b)) => a.cmp(b),
(Value::Record(a), Value::Record(b)) => a.cmp(b),
(Value::Enum(a), Value::Enum(b)) => a.cmp(b),
(Value::Optional(a), Value::Optional(b)) => a.cmp(b),
(Value::Result(a), Value::Result(b)) => match (a, b) {
(Ok(av), Ok(bv)) => av.cmp(bv),
(Err(ae), Err(be)) => ae.cmp(be),
(Ok(_), Err(_)) => Ordering::Less,
(Err(_), Ok(_)) => Ordering::Greater,
},
(
Value::Range {
start: s1,
end: e1,
inclusive: i1,
step: st1,
},
Value::Range {
start: s2,
end: e2,
inclusive: i2,
step: st2,
},
) => (s1, e1, i1, st1).cmp(&(s2, e2, i2, st2)),
(Value::Function(_), Value::Function(_)) => {
unreachable!("function values are not orderable and cannot be used as map keys or set elements")
}
(Value::Iterator(_), Value::Iterator(_)) => {
unreachable!("iterator values are not orderable and cannot be used as map keys or set elements")
}
(Value::StringBuilder(_), Value::StringBuilder(_)) => {
unreachable!("StringBuilder values are not orderable")
}
(Value::Future(_), Value::Future(_)) => {
unreachable!("Future values are not orderable")
}
(Value::Channel(_), Value::Channel(_)) => {
unreachable!("Channel values are not orderable")
}
(Value::Duration(a), Value::Duration(b)) => a.cmp(b),
(Value::Instant(a), Value::Instant(b)) => a.cmp(b),
_ => variant_ord(self).cmp(&variant_ord(other)),
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
variant_ord(self).hash(state);
match self {
Value::Int(v) => v.hash(state),
Value::Float(v) => v.hash(state),
Value::Bool(v) => v.hash(state),
Value::String(v) => v.hash(state),
Value::Char(v) => v.hash(state),
Value::Void => {}
Value::List(v) => v.hash(state),
Value::Tuple(v) => v.hash(state),
Value::Record(v) => v.hash(state),
Value::Enum(v) => v.hash(state),
Value::Function(v) => v.hash(state),
Value::Optional(v) => v.hash(state),
Value::Set(v) => {
for item in v {
item.hash(state);
}
}
Value::Map(v) => {
for (k, val) in v {
k.hash(state);
val.hash(state);
}
}
Value::Result(v) => match v {
Ok(inner) => {
0u8.hash(state);
inner.hash(state);
}
Err(inner) => {
1u8.hash(state);
inner.hash(state);
}
},
Value::Range {
start,
end,
inclusive,
step,
} => {
start.hash(state);
end.hash(state);
inclusive.hash(state);
step.hash(state);
}
Value::Iterator(v) => v.hash(state),
Value::StringBuilder(v) => v.lock().unwrap().hash(state),
Value::Future(v) => (Arc::as_ptr(v) as usize).hash(state),
Value::Duration(v) => v.hash(state),
Value::Instant(v) => v.hash(state),
Value::Channel(v) => (Arc::as_ptr(v) as usize).hash(state),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Int(v) => write!(f, "{v}"),
Value::Float(v) => write!(f, "{v}"),
Value::Bool(true) => write!(f, "true"),
Value::Bool(false) => write!(f, "false"),
Value::String(v) => write!(f, "{v}"),
Value::Char(v) => write!(f, "'{v}'"),
Value::Void => write!(f, "void"),
Value::List(items) => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
Value::Set(items) => {
write!(f, "{{")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "}}")
}
Value::Map(items) => {
write!(f, "{{")?;
for (i, (k, v)) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
Value::Tuple(items) => {
write!(f, "(")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, ")")
}
Value::Record(r) => {
write!(f, "{} {{", r.type_name)?;
for (i, (k, v)) in r.fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, "}}")
}
Value::Enum(e) => {
write!(f, "{}.{}", e.type_name, e.variant)?;
if let Some(payload) = &e.payload {
write!(f, "({payload})")?;
}
Ok(())
}
Value::Function(fn_val) => write!(f, "{fn_val}"),
Value::Optional(Some(v)) => write!(f, "Some({v})"),
Value::Optional(None) => write!(f, "None"),
Value::Result(Ok(v)) => write!(f, "Ok({v})"),
Value::Result(Err(e)) => write!(f, "Err({e})"),
Value::Range {
start,
end,
inclusive,
step,
} => {
if *step == 1 {
if *inclusive {
write!(f, "{start}..={end}")
} else {
write!(f, "{start}..{end}")
}
} else if *inclusive {
write!(f, "{start}..={end} step {step}")
} else {
write!(f, "{start}..{end} step {step}")
}
}
Value::Iterator(v) => write!(f, "{v}"),
Value::StringBuilder(v) => write!(f, "<StringBuilder len={}>", v.lock().unwrap().len()),
Value::Future(_) => write!(f, "<future>"),
Value::Duration(nanos) => write!(f, "{}", format_duration(*nanos)),
Value::Instant(_) => write!(f, "<instant>"),
Value::Channel(_) => write!(f, "<channel>"),
}
}
}
fn format_duration(nanos: i64) -> String {
if nanos == 0 {
return "0s".to_string();
}
let sign = if nanos < 0 { "-" } else { "" };
let abs_nanos = nanos.unsigned_abs();
if abs_nanos >= 1_000_000_000 {
let secs = abs_nanos as f64 / 1_000_000_000.0;
format!("{sign}{secs}s")
} else if abs_nanos >= 1_000_000 {
let ms = abs_nanos as f64 / 1_000_000.0;
format!("{sign}{ms}ms")
} else if abs_nanos >= 1_000 {
let us = abs_nanos as f64 / 1_000.0;
format!("{sign}{us}µs")
} else {
format!("{sign}{abs_nanos}ns")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ordf64_total_order_neg_zero_vs_pos_zero() {
let neg = OrdF64(-0.0_f64);
let pos = OrdF64(0.0_f64);
assert!(neg < pos, "-0.0 should be less than +0.0 under total order");
}
#[test]
fn ordf64_nan_is_ordered() {
let nan = OrdF64(f64::NAN);
let inf = OrdF64(f64::INFINITY);
assert!(inf < nan, "+Inf should be less than +NaN under total order");
}
#[test]
fn ordf64_equality_uses_total_cmp() {
assert_ne!(OrdF64(-0.0), OrdF64(0.0));
assert_eq!(OrdF64(1.0), OrdF64(1.0));
}
#[test]
fn bock_string_ord() {
let a = BockString::new("apple");
let b = BockString::new("banana");
assert!(a < b);
}
#[test]
fn bock_string_display() {
let s = BockString::new("hello");
assert_eq!(s.to_string(), "hello");
}
#[test]
fn int_equality() {
assert_eq!(Value::Int(42), Value::Int(42));
assert_ne!(Value::Int(1), Value::Int(2));
}
#[test]
fn float_equality() {
assert_eq!(Value::Float(OrdF64(1.5)), Value::Float(OrdF64(1.5)));
assert_ne!(Value::Float(OrdF64(-0.0)), Value::Float(OrdF64(0.0)));
}
#[test]
fn bool_equality() {
assert_eq!(Value::Bool(true), Value::Bool(true));
assert_ne!(Value::Bool(true), Value::Bool(false));
}
#[test]
fn string_equality() {
assert_eq!(
Value::String(BockString::new("hi")),
Value::String(BockString::new("hi"))
);
}
#[test]
fn void_equality() {
assert_eq!(Value::Void, Value::Void);
}
#[test]
fn different_variants_not_equal() {
assert_ne!(Value::Int(0), Value::Bool(false));
}
#[test]
fn fn_equality_by_identity() {
let f1 = FnValue::new_named("foo");
let f2 = FnValue::new_named("foo");
assert_eq!(f1, f1.clone());
assert_ne!(f1, f2);
}
#[test]
fn fn_value_equality_by_identity() {
let f1 = FnValue::new_anonymous();
let f2 = FnValue::new_anonymous();
let v1 = Value::Function(f1.clone());
let v1_clone = Value::Function(f1);
let v2 = Value::Function(f2);
assert_eq!(v1, v1_clone);
assert_ne!(v1_clone, v2);
}
#[test]
#[should_panic(expected = "function values are not orderable")]
fn fn_value_ordering_panics() {
let f1 = Value::Function(FnValue::new_anonymous());
let f2 = Value::Function(FnValue::new_anonymous());
let _ = f1.cmp(&f2);
}
#[test]
fn int_ordering() {
assert!(Value::Int(1) < Value::Int(2));
assert!(Value::Int(2) > Value::Int(1));
}
#[test]
fn bool_ordering() {
assert!(Value::Bool(false) < Value::Bool(true));
}
#[test]
fn optional_none_less_than_some() {
assert!(Value::Optional(None) < Value::Optional(Some(Box::new(Value::Int(0)))));
}
#[test]
fn result_ok_less_than_err() {
let ok = Value::Result(Ok(Box::new(Value::Int(0))));
let err = Value::Result(Err(Box::new(Value::Int(0))));
assert!(ok < err);
}
#[test]
fn cross_variant_ordering_by_discriminant() {
assert!(Value::Void < Value::Bool(false));
assert!(Value::Bool(false) < Value::Int(0));
}
#[test]
fn value_as_btreemap_key() {
let mut map = BTreeMap::new();
map.insert(Value::Int(1), Value::String(BockString::new("one")));
map.insert(Value::Int(2), Value::String(BockString::new("two")));
assert_eq!(
map.get(&Value::Int(1)),
Some(&Value::String(BockString::new("one")))
);
}
#[test]
fn value_as_btreeset_element() {
let mut set = BTreeSet::new();
set.insert(Value::Int(3));
set.insert(Value::Int(1));
set.insert(Value::Int(2));
let sorted: Vec<_> = set.iter().collect();
assert_eq!(sorted[0], &Value::Int(1));
assert_eq!(sorted[2], &Value::Int(3));
}
#[test]
fn float_as_btreeset_element() {
let mut set = BTreeSet::new();
set.insert(Value::Float(OrdF64(3.0)));
set.insert(Value::Float(OrdF64(1.0)));
set.insert(Value::Float(OrdF64(2.0)));
let mut iter = set.iter();
assert_eq!(iter.next(), Some(&Value::Float(OrdF64(1.0))));
}
#[test]
fn display_primitives() {
assert_eq!(Value::Int(42).to_string(), "42");
assert_eq!(Value::Float(OrdF64(3.14)).to_string(), "3.14");
assert_eq!(Value::Bool(true).to_string(), "true");
assert_eq!(Value::Bool(false).to_string(), "false");
assert_eq!(Value::String(BockString::new("hi")).to_string(), "hi");
assert_eq!(Value::Char('x').to_string(), "'x'");
assert_eq!(Value::Void.to_string(), "void");
}
#[test]
fn display_list() {
let v = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
assert_eq!(v.to_string(), "[1, 2, 3]");
}
#[test]
fn display_tuple() {
let v = Value::Tuple(vec![Value::Int(1), Value::Bool(true)]);
assert_eq!(v.to_string(), "(1, true)");
}
#[test]
fn display_optional() {
assert_eq!(
Value::Optional(Some(Box::new(Value::Int(5)))).to_string(),
"Some(5)"
);
assert_eq!(Value::Optional(None).to_string(), "None");
}
#[test]
fn display_result() {
assert_eq!(
Value::Result(Ok(Box::new(Value::Int(0)))).to_string(),
"Ok(0)"
);
assert_eq!(
Value::Result(Err(Box::new(Value::String(BockString::new("fail"))))).to_string(),
"Err(fail)"
);
}
#[test]
fn display_enum_without_payload() {
let v = Value::Enum(EnumValue {
type_name: "Color".into(),
variant: "Red".into(),
payload: None,
});
assert_eq!(v.to_string(), "Color.Red");
}
#[test]
fn display_enum_with_payload() {
let v = Value::Enum(EnumValue {
type_name: "Shape".into(),
variant: "Circle".into(),
payload: Some(Box::new(Value::Float(OrdF64(1.0)))),
});
assert_eq!(v.to_string(), "Shape.Circle(1)");
}
#[test]
fn display_record() {
let mut fields = BTreeMap::new();
fields.insert("x".to_string(), Value::Int(1));
fields.insert("y".to_string(), Value::Int(2));
let v = Value::Record(RecordValue {
type_name: "Point".into(),
fields,
});
assert_eq!(v.to_string(), "Point {x: 1, y: 2}");
}
#[test]
fn display_function_named() {
let v = Value::Function(FnValue::new_named("add"));
assert_eq!(v.to_string(), "<fn add>");
}
#[test]
fn value_clone() {
let original = Value::List(vec![Value::Int(1), Value::Bool(true)]);
let cloned = original.clone();
assert_eq!(original, cloned);
}
#[test]
fn nested_map_value() {
let inner = Value::Map(BTreeMap::from([(Value::Int(1), Value::Bool(true))]));
let outer = Value::List(vec![inner]);
assert_eq!(outer.to_string(), "[{1: true}]");
}
}