use crate::lex::{Expression, Tensor};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Reference {
pub type_name: Option<Box<str>>,
pub id: Box<str>,
}
impl Reference {
pub fn local(id: impl Into<String>) -> Self {
Self {
type_name: None,
id: id.into().into_boxed_str(),
}
}
pub fn qualified(type_name: impl Into<String>, id: impl Into<String>) -> Self {
Self {
type_name: Some(type_name.into().into_boxed_str()),
id: id.into().into_boxed_str(),
}
}
pub fn unqualified(id: impl Into<String>) -> Self {
Self::local(id)
}
pub fn to_ref_string(&self) -> String {
match &self.type_name {
Some(t) => format!("@{}:{}", t, self.id),
None => format!("@{}", self.id),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(Box<str>),
Tensor(Box<Tensor>),
Reference(Reference),
Expression(Box<Expression>),
List(Box<Vec<Value>>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn is_reference(&self) -> bool {
matches!(self, Self::Reference(_))
}
pub fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}
pub fn as_list(&self) -> Option<&[Value]> {
match self {
Self::List(l) => Some(l),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Self::Int(n) => Some(*n),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Float(n) => Some(*n),
Self::Int(n) => Some(*n as f64),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_reference(&self) -> Option<&Reference> {
match self {
Self::Reference(r) => Some(r),
_ => None,
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "~"),
Self::Bool(b) => write!(f, "{}", b),
Self::Int(n) => write!(f, "{}", n),
Self::Float(n) => write!(f, "{}", n),
Self::String(s) => write!(f, "{}", s),
Self::Tensor(_) => write!(f, "[tensor]"),
Self::Reference(r) => write!(f, "{}", r.to_ref_string()),
Self::Expression(e) => write!(f, "$({})", e),
Self::List(items) => {
write!(f, "(")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
}
}
impl Value {
pub fn as_expression(&self) -> Option<&Expression> {
match self {
Self::Expression(e) => Some(e),
_ => None,
}
}
pub fn coerce_to(
&self,
expected: &crate::types::ExpectedType,
) -> crate::coercion::CoercionResult {
crate::coercion::coerce(
self.clone(),
expected,
crate::coercion::CoercionMode::Lenient,
)
}
pub fn can_coerce_to(&self, expected: &crate::types::ExpectedType) -> bool {
!matches!(
self.coerce_to(expected),
crate::coercion::CoercionResult::Failed { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reference_local() {
let r = Reference::local("user-123");
assert_eq!(r.type_name, None);
assert_eq!(r.id.as_ref(), "user-123");
}
#[test]
fn test_reference_qualified() {
let r = Reference::qualified("User", "123");
assert_eq!(r.type_name.as_deref(), Some("User"));
assert_eq!(r.id.as_ref(), "123");
}
#[test]
fn test_reference_to_ref_string_local() {
let r = Reference::local("id-1");
assert_eq!(r.to_ref_string(), "@id-1");
}
#[test]
fn test_reference_to_ref_string_qualified() {
let r = Reference::qualified("User", "id-1");
assert_eq!(r.to_ref_string(), "@User:id-1");
}
#[test]
fn test_reference_equality() {
let a = Reference::qualified("User", "1");
let b = Reference::qualified("User", "1");
assert_eq!(a, b);
}
#[test]
fn test_reference_inequality() {
let a = Reference::qualified("User", "1");
let b = Reference::qualified("Post", "1");
assert_ne!(a, b);
}
#[test]
fn test_reference_clone() {
let original = Reference::qualified("Type", "id");
let cloned = original.clone();
assert_eq!(original, cloned);
}
#[test]
fn test_reference_debug() {
let r = Reference::qualified("User", "abc");
let debug = format!("{:?}", r);
assert!(debug.contains("User"));
assert!(debug.contains("abc"));
}
#[test]
fn test_value_is_null() {
assert!(Value::Null.is_null());
assert!(!Value::Bool(true).is_null());
assert!(!Value::Int(0).is_null());
}
#[test]
fn test_value_is_reference() {
let r = Reference::local("id");
assert!(Value::Reference(r).is_reference());
assert!(!Value::Null.is_reference());
assert!(!Value::String("@ref".to_string().into()).is_reference());
}
#[test]
fn test_value_as_str() {
let v = Value::String("hello".to_string().into());
assert_eq!(v.as_str(), Some("hello"));
assert_eq!(Value::Null.as_str(), None);
assert_eq!(Value::Int(42).as_str(), None);
}
#[test]
fn test_value_as_int() {
assert_eq!(Value::Int(42).as_int(), Some(42));
assert_eq!(Value::Int(-100).as_int(), Some(-100));
assert_eq!(Value::Float(3.5).as_int(), None);
assert_eq!(Value::String("42".to_string().into()).as_int(), None);
}
#[test]
fn test_value_as_float() {
assert_eq!(Value::Float(3.5).as_float(), Some(3.5));
assert_eq!(Value::Int(42).as_float(), Some(42.0));
assert_eq!(Value::String("3.5".to_string().into()).as_float(), None);
}
#[test]
fn test_value_as_bool() {
assert_eq!(Value::Bool(true).as_bool(), Some(true));
assert_eq!(Value::Bool(false).as_bool(), Some(false));
assert_eq!(Value::Int(1).as_bool(), None);
assert_eq!(Value::String("true".to_string().into()).as_bool(), None);
}
#[test]
fn test_value_as_reference() {
let r = Reference::local("id");
let v = Value::Reference(r.clone());
assert_eq!(v.as_reference(), Some(&r));
assert_eq!(Value::Null.as_reference(), None);
}
#[test]
fn test_value_as_expression() {
use crate::lex::{Expression, Span};
let expr = Expression::Identifier {
name: "x".to_string(),
span: Span::synthetic(),
};
let v = Value::Expression(Box::new(expr.clone()));
assert_eq!(v.as_expression(), Some(&expr));
assert_eq!(Value::Null.as_expression(), None);
}
#[test]
fn test_value_display_null() {
assert_eq!(format!("{}", Value::Null), "~");
}
#[test]
fn test_value_display_bool() {
assert_eq!(format!("{}", Value::Bool(true)), "true");
assert_eq!(format!("{}", Value::Bool(false)), "false");
}
#[test]
fn test_value_display_int() {
assert_eq!(format!("{}", Value::Int(42)), "42");
assert_eq!(format!("{}", Value::Int(-100)), "-100");
assert_eq!(format!("{}", Value::Int(0)), "0");
}
#[test]
fn test_value_display_float() {
let s = format!("{}", Value::Float(3.5));
assert!(s.starts_with("3.5"));
}
#[test]
fn test_value_display_string() {
assert_eq!(
format!("{}", Value::String("hello".to_string().into())),
"hello"
);
}
#[test]
fn test_value_display_reference() {
let r = Reference::qualified("User", "123");
assert_eq!(format!("{}", Value::Reference(r)), "@User:123");
}
#[test]
fn test_value_display_expression() {
use crate::lex::{Expression, Span};
let expr = Expression::Identifier {
name: "x".to_string(),
span: Span::synthetic(),
};
assert_eq!(format!("{}", Value::Expression(Box::new(expr))), "$(x)");
}
#[test]
fn test_value_display_tensor() {
use crate::lex::Tensor;
let t = Tensor::Array(vec![Tensor::Scalar(1.0), Tensor::Scalar(2.0)]);
assert_eq!(format!("{}", Value::Tensor(Box::new(t))), "[tensor]");
}
#[test]
fn test_value_equality_null() {
assert_eq!(Value::Null, Value::Null);
}
#[test]
fn test_value_equality_bool() {
assert_eq!(Value::Bool(true), Value::Bool(true));
assert_ne!(Value::Bool(true), Value::Bool(false));
}
#[test]
fn test_value_equality_int() {
assert_eq!(Value::Int(42), Value::Int(42));
assert_ne!(Value::Int(42), Value::Int(43));
}
#[test]
fn test_value_equality_string() {
assert_eq!(
Value::String("test".to_string().into()),
Value::String("test".to_string().into())
);
assert_ne!(
Value::String("a".to_string().into()),
Value::String("b".to_string().into())
);
}
#[test]
fn test_value_inequality_different_types() {
assert_ne!(Value::Int(1), Value::Bool(true));
assert_ne!(Value::Null, Value::Bool(false));
assert_ne!(Value::String("42".to_string().into()), Value::Int(42));
}
#[test]
fn test_value_clone() {
let values = vec![
Value::Null,
Value::Bool(true),
Value::Int(42),
Value::Float(3.5),
Value::String("test".to_string().into()),
Value::Reference(Reference::local("id")),
];
for v in values {
let cloned = v.clone();
assert_eq!(v, cloned);
}
}
#[test]
fn test_value_debug() {
let v = Value::Int(42);
let debug = format!("{:?}", v);
assert!(debug.contains("Int"));
assert!(debug.contains("42"));
}
#[test]
fn test_value_int_bounds() {
assert_eq!(Value::Int(i64::MAX).as_int(), Some(i64::MAX));
assert_eq!(Value::Int(i64::MIN).as_int(), Some(i64::MIN));
}
#[test]
fn test_value_empty_string() {
let v = Value::String(String::new().into());
assert_eq!(v.as_str(), Some(""));
}
#[test]
fn test_value_unicode_string() {
let v = Value::String("日本語 🎉".to_string().into());
assert_eq!(v.as_str(), Some("日本語 🎉"));
}
#[test]
fn test_value_float_special() {
let inf = Value::Float(f64::INFINITY);
assert!(inf.as_float().unwrap().is_infinite());
let nan = Value::Float(f64::NAN);
assert!(nan.as_float().unwrap().is_nan());
}
#[test]
fn test_reference_empty_id() {
let r = Reference::local("");
assert_eq!(r.id.as_ref(), "");
assert_eq!(r.to_ref_string(), "@");
}
#[test]
fn test_reference_with_special_chars() {
let r = Reference::local("id-with-hyphens-123");
assert_eq!(r.to_ref_string(), "@id-with-hyphens-123");
}
}