thal 0.0.1

Reactive semantic runtime — molecules, reactions, and effect actors for building LLM-backed applications as dataflow programs.
Documentation
use crate::Error;
use std::collections::BTreeMap;

#[derive(Clone, Debug)]
pub enum Value {
    Null,
    Bool(bool),
    Int(i64),
    Float(f64),
    String(String),
    Bytes(Vec<u8>),
    Timestamp(i64),
    Duration(i64),
    Uuid(uuid::Uuid),
    List(Vec<Value>),
    Map(BTreeMap<String, Value>),
    Tuple(Vec<Value>),
    Enum { ty: String, variant: String },
}

impl Value {
    pub fn as_int(&self) -> Result<i64, Error> {
        match self {
            Value::Int(n) => Ok(*n),
            other => Err(Error::TypeMismatch {
                expected: "Int".into(),
                got: other.type_name().into(),
            }),
        }
    }

    pub fn as_duration_ms(&self) -> Result<i64, Error> {
        match self {
            Value::Duration(ms) => Ok(*ms),
            other => Err(Error::TypeMismatch {
                expected: "Duration".into(),
                got: other.type_name().into(),
            }),
        }
    }

    pub fn as_string(&self) -> Result<&str, Error> {
        match self {
            Value::String(s) => Ok(s.as_str()),
            other => Err(Error::TypeMismatch {
                expected: "String".into(),
                got: other.type_name().into(),
            }),
        }
    }

    pub fn as_bool(&self) -> Result<bool, Error> {
        match self {
            Value::Bool(b) => Ok(*b),
            other => Err(Error::TypeMismatch {
                expected: "Bool".into(),
                got: other.type_name().into(),
            }),
        }
    }

    pub fn as_float(&self) -> Result<f64, Error> {
        match self {
            Value::Float(f) => Ok(*f),
            Value::Int(n) => Ok(*n as f64),
            other => Err(Error::TypeMismatch {
                expected: "Float".into(),
                got: other.type_name().into(),
            }),
        }
    }

    pub fn type_name(&self) -> &'static str {
        match self {
            Value::Null => "Null",
            Value::Bool(_) => "Bool",
            Value::Int(_) => "Int",
            Value::Float(_) => "Float",
            Value::String(_) => "String",
            Value::Bytes(_) => "Bytes",
            Value::Timestamp(_) => "Timestamp",
            Value::Duration(_) => "Duration",
            Value::Uuid(_) => "Uuid",
            Value::List(_) => "List",
            Value::Map(_) => "Map",
            Value::Tuple(_) => "Tuple",
            Value::Enum { .. } => "Enum",
        }
    }
}

// Manual PartialEq treats NaN == NaN so Value can implement Eq.
impl PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Value::Null, Value::Null) => true,
            (Value::Bool(a), Value::Bool(b)) => a == b,
            (Value::Int(a), Value::Int(b)) => a == b,
            (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
            (Value::String(a), Value::String(b)) => a == b,
            (Value::Bytes(a), Value::Bytes(b)) => a == b,
            (Value::Timestamp(a), Value::Timestamp(b)) => a == b,
            (Value::Duration(a), Value::Duration(b)) => a == b,
            (Value::Uuid(a), Value::Uuid(b)) => a == b,
            (Value::List(a), Value::List(b)) => a == b,
            (Value::Map(a), Value::Map(b)) => a == b,
            (Value::Tuple(a), Value::Tuple(b)) => a == b,
            (Value::Enum { ty: t1, variant: v1 }, Value::Enum { ty: t2, variant: v2 }) => {
                t1 == t2 && v1 == v2
            }
            _ => false,
        }
    }
}

impl Eq for Value {}

impl PartialOrd for Value {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Value {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        use std::cmp::Ordering;
        if std::mem::discriminant(self) != std::mem::discriminant(other) {
            return self.type_name().cmp(other.type_name());
        }
        match (self, other) {
            (Value::Null, Value::Null) => 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.to_bits().cmp(&b.to_bits()),
            (Value::String(a), Value::String(b)) => a.cmp(b),
            (Value::Bytes(a), Value::Bytes(b)) => a.cmp(b),
            (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
            (Value::Duration(a), Value::Duration(b)) => a.cmp(b),
            (Value::Uuid(a), Value::Uuid(b)) => a.cmp(b),
            (Value::List(a), Value::List(b)) => a.cmp(b),
            (Value::Map(a), Value::Map(b)) => a.cmp(b),
            (Value::Tuple(a), Value::Tuple(b)) => a.cmp(b),
            (Value::Enum { ty: t1, variant: v1 }, Value::Enum { ty: t2, variant: v2 }) => {
                t1.cmp(t2).then_with(|| v1.cmp(v2))
            }
            _ => Ordering::Equal,
        }
    }
}

impl std::hash::Hash for Value {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        std::mem::discriminant(self).hash(state);
        match self {
            Value::Null => {}
            Value::Bool(b) => b.hash(state),
            Value::Int(n) => n.hash(state),
            Value::Float(f) => f.to_bits().hash(state),
            Value::String(s) => s.hash(state),
            Value::Bytes(b) => b.hash(state),
            Value::Timestamp(t) => t.hash(state),
            Value::Duration(d) => d.hash(state),
            Value::Uuid(u) => u.hash(state),
            Value::List(l) => l.hash(state),
            Value::Map(m) => m.hash(state),
            Value::Tuple(t) => t.hash(state),
            Value::Enum { ty, variant } => {
                ty.hash(state);
                variant.hash(state);
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Type {
    Primitive(PrimitiveType),
    List(Box<Type>),
    Optional(Box<Type>),
    Map(Box<Type>, Box<Type>),
    Tuple(Vec<Type>),
    Enum(String),
    Molecule(MoleculeKindId),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PrimitiveType {
    Bool,
    Int,
    Float,
    String,
    Bytes,
    Timestamp,
    Duration,
    Uuid,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MoleculeKindId(pub u32);