use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
pub type DictMap = BTreeMap<String, VmValue>;
pub type HarnStr = arcstr::ArcStr;
pub(crate) enum ValueView<'a, T> {
Nil,
Bool(bool),
Int(i64),
Float(f64),
String(&'a str),
Bytes(&'a [u8]),
Duration(i64),
List(&'a [T]),
Record(&'a BTreeMap<String, T>),
Opaque,
}
pub(crate) trait SemanticValue: Sized {
fn semantic_view(&self) -> ValueView<'_, Self>;
}
#[derive(Debug, Clone)]
pub enum VmValue {
Int(i64),
Float(f64),
String(HarnStr),
Bool(bool),
Nil,
Duration(i64),
List(Arc<Vec<VmValue>>),
Dict(Arc<DictMap>),
}
impl VmValue {
pub fn dict<K>(entries: impl IntoIterator<Item = (K, VmValue)>) -> Self
where
K: Into<String>,
{
Self::Dict(Arc::new(
entries
.into_iter()
.map(|(key, value)| (key.into(), value))
.collect(),
))
}
pub fn is_truthy(&self) -> bool {
match self {
Self::Nil | Self::Bool(false) => false,
Self::Int(value) => *value != 0,
Self::Float(value) => *value != 0.0 && !value.is_nan(),
Self::String(value) => !value.is_empty(),
Self::List(value) => !value.is_empty(),
Self::Dict(value) => !value.is_empty(),
Self::Bool(true) | Self::Duration(_) => true,
}
}
pub fn display(&self) -> String {
self.to_string()
}
}
impl SemanticValue for VmValue {
fn semantic_view(&self) -> ValueView<'_, Self> {
match self {
Self::Nil => ValueView::Nil,
Self::Bool(value) => ValueView::Bool(*value),
Self::Int(value) => ValueView::Int(*value),
Self::Float(value) => ValueView::Float(*value),
Self::String(value) => ValueView::String(value),
Self::Duration(value) => ValueView::Duration(*value),
Self::List(values) => ValueView::List(values),
Self::Dict(values) => ValueView::Record(values),
}
}
}
impl fmt::Display for VmValue {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Int(value) => write!(formatter, "{value}"),
Self::Float(value) => write!(formatter, "{value}"),
Self::String(value) => formatter.write_str(value),
Self::Bool(value) => write!(formatter, "{value}"),
Self::Nil => formatter.write_str("nil"),
Self::Duration(value) => write!(formatter, "{value}ms"),
Self::List(values) => {
formatter.write_str("[")?;
for (index, value) in values.iter().enumerate() {
if index > 0 {
formatter.write_str(", ")?;
}
write!(formatter, "{value}")?;
}
formatter.write_str("]")
}
Self::Dict(entries) => {
formatter.write_str("{")?;
for (index, (key, value)) in entries.iter().enumerate() {
if index > 0 {
formatter.write_str(", ")?;
}
write!(formatter, "{key}: {value}")?;
}
formatter.write_str("}")
}
}
}
}
pub fn values_equal(left: &VmValue, right: &VmValue) -> bool {
semantic_values_equal(left, right)
}
pub fn try_compare_values(left: &VmValue, right: &VmValue) -> Option<i8> {
semantic_try_compare(left, right)
}
pub(crate) fn semantic_values_equal<T: SemanticValue>(left: &T, right: &T) -> bool {
let mut pending = vec![(left, right)];
while let Some((left, right)) = pending.pop() {
match (left.semantic_view(), right.semantic_view()) {
(ValueView::Nil, ValueView::Nil) => {}
(ValueView::Bool(left), ValueView::Bool(right)) if left == right => {}
(ValueView::Int(left), ValueView::Int(right)) if left == right => {}
(ValueView::Float(left), ValueView::Float(right)) if left == right => {}
(ValueView::Int(left), ValueView::Float(right)) if left as f64 == right => {}
(ValueView::Float(left), ValueView::Int(right)) if left == right as f64 => {}
(ValueView::String(left), ValueView::String(right)) if left == right => {}
(ValueView::Bytes(left), ValueView::Bytes(right)) if left == right => {}
(ValueView::Duration(left), ValueView::Duration(right)) if left == right => {}
(ValueView::List(left), ValueView::List(right)) if left.len() == right.len() => {
pending.extend(left.iter().zip(right.iter()));
}
(ValueView::Record(left), ValueView::Record(right)) if left.len() == right.len() => {
for (key, left_value) in left {
let Some(right_value) = right.get(key) else {
return false;
};
pending.push((left_value, right_value));
}
}
_ => return false,
}
}
true
}
enum CompareTask<'a, T> {
Values(&'a T, &'a T),
ListLength(usize, usize),
}
pub(crate) fn semantic_try_compare<T: SemanticValue>(left: &T, right: &T) -> Option<i8> {
use std::cmp::Ordering;
let mut pending = vec![CompareTask::Values(left, right)];
while let Some(task) = pending.pop() {
let ordering = match task {
CompareTask::ListLength(left, right) => left.cmp(&right),
CompareTask::Values(left, right) => match (left.semantic_view(), right.semantic_view())
{
(ValueView::Int(left), ValueView::Int(right)) => left.cmp(&right),
(ValueView::Float(left), ValueView::Float(right)) => left.partial_cmp(&right)?,
(ValueView::Int(left), ValueView::Float(right)) => {
(left as f64).partial_cmp(&right)?
}
(ValueView::Float(left), ValueView::Int(right)) => {
left.partial_cmp(&(right as f64))?
}
(ValueView::String(left), ValueView::String(right)) => left.cmp(right),
(ValueView::List(left), ValueView::List(right)) => {
pending.push(CompareTask::ListLength(left.len(), right.len()));
for (left, right) in left.iter().zip(right.iter()).rev() {
pending.push(CompareTask::Values(left, right));
}
continue;
}
_ => Ordering::Equal,
},
};
match ordering {
Ordering::Less => return Some(-1),
Ordering::Greater => return Some(1),
Ordering::Equal => {}
}
}
Some(0)
}
pub fn intern_key(key: &str) -> String {
key.to_owned()
}
pub trait VmDictExt {
fn put_str(&mut self, key: &str, value: &str);
}
impl VmDictExt for DictMap {
fn put_str(&mut self, key: &str, value: &str) {
self.insert(key.to_string(), VmValue::String(value.into()));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn list(values: Vec<VmValue>) -> VmValue {
VmValue::List(Arc::new(values))
}
#[test]
fn shared_ordering_is_iterative_lexicographic_and_nan_aware() {
assert_eq!(
try_compare_values(
&list(vec![VmValue::Int(1), VmValue::Int(2)]),
&list(vec![VmValue::Int(1), VmValue::Int(3)]),
),
Some(-1)
);
assert_eq!(
try_compare_values(
&list(vec![VmValue::Int(1)]),
&list(vec![VmValue::Int(1), VmValue::Int(0)])
),
Some(-1)
);
assert_eq!(
try_compare_values(
&list(vec![VmValue::Float(f64::NAN)]),
&list(vec![VmValue::Int(1)]),
),
None
);
}
#[test]
fn non_orderable_values_retain_native_relational_fallback() {
assert_eq!(
try_compare_values(&VmValue::Bool(false), &VmValue::Bool(true)),
Some(0)
);
assert_eq!(
try_compare_values(&VmValue::Nil, &VmValue::Dict(Arc::new(BTreeMap::new()))),
Some(0)
);
}
}