use crate::ast::*;
use crate::parser::Loc;
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::str::FromStr;
use std::sync::Arc;
use educe::Educe;
use itertools::Itertools;
use miette::Diagnostic;
use smol_str::SmolStr;
use thiserror::Error;
#[derive(Educe, Debug, Clone)]
#[educe(PartialEq, Eq, PartialOrd, Ord)]
pub struct Value {
pub value: ValueKind,
#[educe(PartialEq(ignore))]
#[educe(PartialOrd(ignore))]
pub loc: Option<Loc>,
}
#[derive(Debug, Clone, PartialOrd, Ord)]
pub enum ValueKind {
Lit(Literal),
Set(Set),
Record(Arc<BTreeMap<SmolStr, Value>>),
ExtensionValue(Arc<RepresentableExtensionValue>),
}
impl Value {
pub fn empty_set(loc: Option<Loc>) -> Self {
Self {
value: ValueKind::empty_set(),
loc,
}
}
pub fn empty_record(loc: Option<Loc>) -> Self {
Self {
value: ValueKind::empty_record(),
loc,
}
}
pub fn new(value: impl Into<ValueKind>, loc: Option<Loc>) -> Self {
Self {
value: value.into(),
loc,
}
}
pub fn set(vals: impl IntoIterator<Item = Value>, loc: Option<Loc>) -> Self {
Self {
value: ValueKind::set(vals),
loc,
}
}
pub fn set_of_lits(lits: impl IntoIterator<Item = Literal>, loc: Option<Loc>) -> Self {
Self {
value: ValueKind::set_of_lits(lits),
loc,
}
}
pub fn record<K: Into<SmolStr>, V: Into<Value>>(
pairs: impl IntoIterator<Item = (K, V)>,
loc: Option<Loc>,
) -> Self {
Self {
value: ValueKind::record(pairs),
loc,
}
}
pub fn record_arc(pairs: Arc<BTreeMap<SmolStr, Value>>, loc: Option<Loc>) -> Self {
Self {
value: ValueKind::record_arc(pairs),
loc,
}
}
pub fn with_maybe_source_loc(self, loc: Option<Loc>) -> Self {
Self { loc, ..self }
}
pub fn value_kind(&self) -> &ValueKind {
&self.value
}
pub fn source_loc(&self) -> Option<&Loc> {
self.loc.as_ref()
}
pub(crate) fn try_as_lit(&self) -> Option<&Literal> {
self.value.try_as_lit()
}
pub fn eq_and_same_source_loc(&self, other: &Self) -> bool {
self == other && self.source_loc() == other.source_loc()
}
pub fn all_literal_uids(&self) -> HashSet<EntityUID> {
self.value.all_literal_uids()
}
}
impl BoundedDisplay for Value {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
BoundedDisplay::fmt(&self.value, f, n)
}
}
impl ValueKind {
pub fn empty_set() -> Self {
Self::Set(Set::empty())
}
pub fn empty_record() -> Self {
Self::Record(Arc::new(BTreeMap::new()))
}
pub fn set(vals: impl IntoIterator<Item = Value>) -> Self {
Self::Set(Set::new(vals))
}
pub fn set_of_lits(lits: impl IntoIterator<Item = Literal>) -> Self {
Self::Set(Set::from_lits(lits))
}
pub fn record<K: Into<SmolStr>, V: Into<Value>>(
pairs: impl IntoIterator<Item = (K, V)>,
) -> Self {
Self::Record(Arc::new(
pairs
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
))
}
pub fn record_arc(pairs: Arc<BTreeMap<SmolStr, Value>>) -> Self {
Self::Record(pairs)
}
pub(crate) fn try_as_lit(&self) -> Option<&Literal> {
match &self {
Self::Lit(lit) => Some(lit),
_ => None,
}
}
pub fn all_literal_uids(&self) -> HashSet<EntityUID> {
match self {
ValueKind::Lit(Literal::EntityUID(uid)) => HashSet::from([uid.as_ref().clone()]),
ValueKind::Lit(_) => HashSet::new(),
ValueKind::Set(set) => set.iter().flat_map(Value::all_literal_uids).collect(),
ValueKind::Record(record) => {
record.values().flat_map(Value::all_literal_uids).collect()
}
ValueKind::ExtensionValue(_) => HashSet::new(),
}
}
}
impl BoundedDisplay for ValueKind {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
match self {
Self::Lit(lit) => write!(f, "{lit}"),
Self::Set(Set {
fast,
authoritative,
}) => {
write!(f, "[")?;
let truncated = n.map(|n| authoritative.len() > n).unwrap_or(false);
if let Some(rc) = fast {
let elements = match n {
Some(n) => Box::new(rc.as_ref().iter().sorted_unstable().take(n))
as Box<dyn Iterator<Item = &Literal>>,
None => Box::new(rc.as_ref().iter().sorted_unstable())
as Box<dyn Iterator<Item = &Literal>>,
};
for (i, item) in elements.enumerate() {
write!(f, "{item}")?;
if i < authoritative.len() - 1 {
write!(f, ", ")?;
}
}
} else {
let elements = match n {
Some(n) => Box::new(authoritative.as_ref().iter().take(n))
as Box<dyn Iterator<Item = &Value>>,
None => Box::new(authoritative.as_ref().iter())
as Box<dyn Iterator<Item = &Value>>,
};
for (i, item) in elements.enumerate() {
BoundedDisplay::fmt(item, f, n)?;
if i < authoritative.len() - 1 {
write!(f, ", ")?;
}
}
}
if truncated {
write!(f, ".. ")?;
}
write!(f, "]")?;
Ok(())
}
Self::Record(record) => {
write!(f, "{{")?;
let truncated = n.map(|n| record.len() > n).unwrap_or(false);
let elements = match n {
Some(n) => Box::new(record.as_ref().iter().take(n))
as Box<dyn Iterator<Item = (&SmolStr, &Value)>>,
None => Box::new(record.as_ref().iter())
as Box<dyn Iterator<Item = (&SmolStr, &Value)>>,
};
for (i, (k, v)) in elements.enumerate() {
match UnreservedId::from_str(k) {
Ok(k) => {
write!(f, "{k}: ")?;
}
Err(_) => {
write!(f, "\"{k}\": ")?;
}
}
BoundedDisplay::fmt(v, f, n)?;
if i < record.len() - 1 {
write!(f, ", ")?;
}
}
if truncated {
write!(f, ".. ")?;
}
write!(f, "}}")?;
Ok(())
}
Self::ExtensionValue(ev) => write!(f, "{}", RestrictedExpr::from(ev.as_ref().clone())),
}
}
}
#[derive(Debug, Error)]
pub enum NotValue {
#[error("not a value")]
NotValue {
loc: Option<Loc>,
},
}
impl Diagnostic for NotValue {
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
match self {
Self::NotValue { loc } => loc.as_ref().map(|loc| {
Box::new(std::iter::once(miette::LabeledSpan::underline(loc.span))) as _
}),
}
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
match self {
Self::NotValue { loc } => loc.as_ref().map(|loc| &loc.src as &dyn miette::SourceCode),
}
}
}
impl TryFrom<Expr> for Value {
type Error = NotValue;
fn try_from(expr: Expr) -> Result<Self, Self::Error> {
let loc = expr.source_loc().cloned();
Ok(Self {
value: ValueKind::try_from(expr)?,
loc,
})
}
}
impl TryFrom<Expr> for ValueKind {
type Error = NotValue;
fn try_from(expr: Expr) -> Result<Self, Self::Error> {
let loc = expr.source_loc().cloned();
match expr.into_expr_kind() {
ExprKind::Lit(lit) => Ok(Self::Lit(lit)),
ExprKind::Unknown(_) => Err(NotValue::NotValue { loc }),
ExprKind::Var(_) => Err(NotValue::NotValue { loc }),
ExprKind::Slot(_) => Err(NotValue::NotValue { loc }),
ExprKind::If { .. } => Err(NotValue::NotValue { loc }),
ExprKind::And { .. } => Err(NotValue::NotValue { loc }),
ExprKind::Or { .. } => Err(NotValue::NotValue { loc }),
ExprKind::UnaryApp { .. } => Err(NotValue::NotValue { loc }),
ExprKind::BinaryApp { .. } => Err(NotValue::NotValue { loc }),
ExprKind::ExtensionFunctionApp { .. } => Err(NotValue::NotValue { loc }),
ExprKind::GetAttr { .. } => Err(NotValue::NotValue { loc }),
ExprKind::HasAttr { .. } => Err(NotValue::NotValue { loc }),
ExprKind::Like { .. } => Err(NotValue::NotValue { loc }),
ExprKind::Is { .. } => Err(NotValue::NotValue { loc }),
ExprKind::Set(members) => members
.iter()
.map(|e| Value::try_from(e.clone()))
.collect::<Result<Set, _>>()
.map(Self::Set),
ExprKind::Record(map) => map
.iter()
.map(|(k, v)| Value::try_from(v.clone()).map(|v| (k.clone(), v)))
.collect::<Result<BTreeMap<SmolStr, Value>, _>>()
.map(|m| Self::Record(Arc::new(m))),
#[cfg(feature = "tolerant-ast")]
ExprKind::Error { .. } => Err(NotValue::NotValue { loc }),
}
}
}
#[derive(Debug, Clone)]
pub struct Set {
pub authoritative: Arc<BTreeSet<Value>>,
pub fast: Option<Arc<HashSet<Literal>>>,
}
impl Set {
pub fn empty() -> Self {
Self {
authoritative: Arc::new(BTreeSet::new()),
fast: Some(Arc::new(HashSet::new())),
}
}
pub fn new(vals: impl IntoIterator<Item = Value>) -> Self {
let authoritative: BTreeSet<Value> = vals.into_iter().collect();
let fast: Option<Arc<HashSet<Literal>>> = authoritative
.iter()
.map(|v| v.try_as_lit().cloned())
.collect::<Option<HashSet<Literal>>>()
.map(Arc::new);
Self {
authoritative: Arc::new(authoritative),
fast,
}
}
pub fn from_lits(lits: impl IntoIterator<Item = Literal>) -> Self {
let fast: HashSet<Literal> = lits.into_iter().collect();
let authoritative: BTreeSet<Value> = fast
.iter()
.map(|lit| Value {
value: ValueKind::Lit(lit.clone()),
loc: None,
})
.collect();
Self {
authoritative: Arc::new(authoritative),
fast: Some(Arc::new(fast)),
}
}
pub fn len(&self) -> usize {
self.authoritative.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> impl Iterator<Item = &Value> {
self.authoritative.iter()
}
pub fn is_subset(&self, other: &Set) -> bool {
match (&self.fast, &other.fast) {
(Some(ls1), Some(ls2)) => ls1.is_subset(ls2.as_ref()),
(None, Some(_)) => false,
_ => self.authoritative.is_subset(&other.authoritative),
}
}
pub fn is_disjoint(&self, other: &Set) -> bool {
match (&self.fast, &other.fast) {
(Some(ls1), Some(ls2)) => ls1.is_disjoint(ls2.as_ref()),
_ => self.authoritative.is_disjoint(&other.authoritative),
}
}
pub fn contains(&self, value: &Value) -> bool {
match (&self.fast, &value.value) {
(Some(ls), ValueKind::Lit(lit)) => ls.contains(lit),
(Some(_), _) => false,
_ => self.authoritative.contains(value),
}
}
}
impl FromIterator<Value> for Set {
fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
let (literals, non_literals): (BTreeSet<_>, BTreeSet<_>) = iter
.into_iter()
.partition(|v| matches!(&v.value, ValueKind::Lit { .. }));
if non_literals.is_empty() {
Self::from_iter(literals.into_iter().map(|v| match v {
Value {
value: ValueKind::Lit(lit),
..
} => lit,
#[expect(clippy::unreachable, reason = "This is unreachable as every item in `literals` matches ValueKind::Lit")]
_ => unreachable!(),
}))
} else {
let mut all_items = non_literals;
let mut literals = literals;
all_items.append(&mut literals);
Self {
authoritative: Arc::new(all_items),
fast: None,
}
}
}
}
impl FromIterator<Literal> for Set {
fn from_iter<T: IntoIterator<Item = Literal>>(iter: T) -> Self {
let fast: HashSet<Literal> = iter.into_iter().collect();
Self {
authoritative: Arc::new(fast.iter().cloned().map(Into::into).collect()),
fast: Some(Arc::new(fast)),
}
}
}
impl PartialEq for ValueKind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ValueKind::Lit(lit1), ValueKind::Lit(lit2)) => lit1 == lit2,
(ValueKind::Set(set1), ValueKind::Set(set2)) => set1 == set2,
(ValueKind::Record(r1), ValueKind::Record(r2)) => r1 == r2,
(ValueKind::ExtensionValue(ev1), ValueKind::ExtensionValue(ev2)) => ev1 == ev2,
(_, _) => false, }
}
}
impl Eq for ValueKind {}
impl PartialEq for Set {
fn eq(&self, other: &Self) -> bool {
match (self.fast.as_ref(), other.fast.as_ref()) {
(Some(rc1), Some(rc2)) => rc1 == rc2,
(Some(_), None) => false, (None, Some(_)) => false, (None, None) => self.authoritative.as_ref() == other.authoritative.as_ref(),
}
}
}
impl Eq for Set {}
impl Ord for Set {
fn cmp(&self, other: &Set) -> std::cmp::Ordering {
self.authoritative
.as_ref()
.cmp(other.authoritative.as_ref())
}
}
impl PartialOrd<Set> for Set {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl StaticallyTyped for Value {
fn type_of(&self) -> Type {
self.value.type_of()
}
}
impl StaticallyTyped for ValueKind {
fn type_of(&self) -> Type {
match self {
Self::Lit(lit) => lit.type_of(),
Self::Set(_) => Type::Set,
Self::Record(_) => Type::Record,
Self::ExtensionValue(ev) => ev.type_of(),
}
}
}
pub trait BoundedDisplay {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result;
fn fmt_bounded(&self, f: &mut impl std::fmt::Write, n: usize) -> std::fmt::Result {
self.fmt(f, Some(n))
}
fn fmt_unbounded(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
self.fmt(f, None)
}
}
pub trait BoundedToString {
fn to_string(&self, n: Option<usize>) -> String;
fn to_string_bounded(&self, n: usize) -> String {
self.to_string(Some(n))
}
fn to_string_unbounded(&self) -> String {
self.to_string(None)
}
}
impl<T: BoundedDisplay> BoundedToString for T {
fn to_string(&self, n: Option<usize>) -> String {
let mut s = String::new();
#[expect(clippy::expect_used, reason = "`std::fmt::Write` does not return errors when writing to a `String`")]
BoundedDisplay::fmt(self, &mut s, n).expect("a `BoundedDisplay` implementation returned an error when writing to a `String`, which shouldn't happen");
s
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)
}
}
impl std::fmt::Display for ValueKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
BoundedDisplay::fmt_unbounded(self, f)
}
}
impl<T: Into<Value>> From<Vec<T>> for Value {
fn from(v: Vec<T>) -> Self {
Self::set(v.into_iter().map(Into::into), None)
}
}
impl<T: Into<Value>> From<Vec<T>> for ValueKind {
fn from(v: Vec<T>) -> Self {
Self::set(v.into_iter().map(Into::into))
}
}
impl<T: Into<Literal>> From<T> for Value {
fn from(lit: T) -> Self {
Self {
value: lit.into().into(),
loc: None,
}
}
}
impl<T: Into<Literal>> From<T> for ValueKind {
fn from(lit: T) -> Self {
Self::Lit(lit.into())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn values() {
assert_eq!(
Value::from(true),
Value {
value: ValueKind::Lit(Literal::Bool(true)),
loc: None,
},
);
assert_eq!(
Value::from(false),
Value {
value: ValueKind::Lit(Literal::Bool(false)),
loc: None,
},
);
assert_eq!(
Value::from(23),
Value {
value: ValueKind::Lit(Literal::Long(23)),
loc: None,
},
);
assert_eq!(
Value::from(-47),
Value {
value: ValueKind::Lit(Literal::Long(-47)),
loc: None,
},
);
assert_eq!(
Value::from("hello"),
Value {
value: ValueKind::Lit(Literal::String("hello".into())),
loc: None,
},
);
assert_eq!(
Value::from("hello".to_owned()),
Value {
value: ValueKind::Lit(Literal::String("hello".into())),
loc: None,
},
);
assert_eq!(
Value::from(String::new()),
Value {
value: ValueKind::Lit(Literal::String(SmolStr::default())),
loc: None,
},
);
assert_eq!(
Value::from(""),
Value {
value: ValueKind::Lit(Literal::String(SmolStr::default())),
loc: None,
},
);
assert_eq!(
Value::from(vec![2, -3, 40]),
Value::set(vec![Value::from(2), Value::from(-3), Value::from(40)], None),
);
assert_eq!(
Value::from(vec![Literal::from(false), Literal::from("eggs")]),
Value::set(vec![Value::from(false), Value::from("eggs")], None),
);
assert_eq!(
Value::set(vec![Value::from(false), Value::from("eggs")], None),
Value::set_of_lits(vec![Literal::from(false), Literal::from("eggs")], None),
);
let mut rec1: BTreeMap<SmolStr, Value> = BTreeMap::new();
rec1.insert("ham".into(), 3.into());
rec1.insert("eggs".into(), "hickory".into());
assert_eq!(
Value::record(rec1.clone(), None),
Value {
value: ValueKind::Record(Arc::new(rec1)),
loc: None,
},
);
let mut rec2: BTreeMap<SmolStr, Value> = BTreeMap::new();
rec2.insert("hi".into(), "ham".into());
rec2.insert("eggs".into(), "hickory".into());
assert_eq!(
Value::record(vec![("hi", "ham"), ("eggs", "hickory"),], None),
Value {
value: ValueKind::Record(Arc::new(rec2)),
loc: None,
},
);
assert_eq!(
Value::from(EntityUID::with_eid("foo")),
Value {
value: ValueKind::Lit(Literal::EntityUID(Arc::new(EntityUID::with_eid("foo")))),
loc: None,
},
);
}
#[test]
fn value_types() {
assert_eq!(Value::from(false).type_of(), Type::Bool);
assert_eq!(Value::from(23).type_of(), Type::Long);
assert_eq!(Value::from(-47).type_of(), Type::Long);
assert_eq!(Value::from("hello").type_of(), Type::String);
assert_eq!(Value::from(vec![2, -3, 40]).type_of(), Type::Set);
assert_eq!(Value::empty_set(None).type_of(), Type::Set);
assert_eq!(Value::empty_record(None).type_of(), Type::Record);
assert_eq!(
Value::record(vec![("hello", Value::from("ham"))], None).type_of(),
Type::Record
);
assert_eq!(
Value::from(EntityUID::with_eid("foo")).type_of(),
Type::entity_type(
Name::parse_unqualified_name("test_entity_type").expect("valid identifier")
)
);
}
#[test]
fn test_set_is_empty_for_empty_set() {
let set = Set {
authoritative: Arc::new(BTreeSet::new()),
fast: Some(Arc::new(HashSet::new())),
};
assert!(set.is_empty());
}
#[test]
fn test_set_is_not_empty_for_set_with_values() {
let set = Set {
authoritative: Arc::new(BTreeSet::from([Value::from("abc")])),
fast: None,
};
assert!(!set.is_empty());
}
#[test]
fn pretty_printer() {
assert_eq!(ToString::to_string(&Value::from("abc")), r#""abc""#);
assert_eq!(ToString::to_string(&Value::from("\t")), r#""\t""#);
assert_eq!(ToString::to_string(&Value::from("🐈")), r#""🐈""#);
}
#[test]
fn set_collect() {
let v = vec![Value {
value: 1.into(),
loc: None,
}];
let set: Set = v.into_iter().collect();
assert_eq!(set.len(), 1);
let v2 = vec![Value {
value: ValueKind::Set(set),
loc: None,
}];
let set2: Set = v2.into_iter().collect();
assert_eq!(set2.len(), 1);
}
}