use super::{BoxedObject, Object, RefValue};
use indexmap::IndexMap;
use tokay_macros::tokay_method;
extern crate self as tokay;
use std::cmp::Ordering;
type InnerDict = IndexMap<String, RefValue>;
#[derive(Debug, Clone)]
pub struct Dict {
dict: InnerDict,
}
impl Object for Dict {
fn severity(&self) -> u8 {
20
}
fn name(&self) -> &'static str {
"dict"
}
fn repr(&self) -> String {
let mut ret = "(".to_string();
for (key, value) in self.iter() {
if ret.len() > 1 {
ret.push_str(", ");
}
if !key.chars().all(|ch| ch.is_alphabetic() || ch == '_') {
ret.push('"');
for ch in key.chars() {
match ch {
'\n' => ret.push_str("\\n"),
'\r' => ret.push_str("\\r"),
'\t' => ret.push_str("\\t"),
'"' => ret.push_str("\\\""),
_ => ret.push(ch),
}
}
ret.push('"');
} else {
ret.push_str(key);
}
ret.push_str(" => ");
ret.push_str(&value.borrow().repr());
}
ret.push(')');
ret
}
fn is_true(&self) -> bool {
self.len() > 0
}
}
impl Dict {
pub fn new() -> Self {
Self {
dict: InnerDict::new(),
}
}
tokay_method!("dict()", Ok(RefValue::from(Dict::new())));
tokay_method!("dict_len(dict)", {
let dict = dict.borrow();
if let Some(dict) = dict.object::<Dict>() {
Ok(RefValue::from(dict.len()))
} else {
Err(format!(
"{} only accepts '{}' as parameter, not '{}'",
__function,
"dict",
dict.name()
))
}
});
tokay_method!("dict_update(dict, other)", {
{
let dict = &mut *dict.borrow_mut();
let other = &*other.borrow();
if let Some(dict) = dict.object_mut::<Dict>() {
if let Some(other) = other.object::<Dict>() {
for (k, v) in other.iter() {
dict.insert(k.clone(), v.clone());
}
} else {
return Err(format!(
"{} only accepts '{}' as second parameter, not '{}'",
__function,
dict.name(),
other.name()
));
}
} else {
return Err(format!(
"{} only accepts '{}' as first parameter, not '{}'",
__function,
"dict",
dict.name()
));
}
}
Ok(dict)
});
}
impl PartialOrd for Dict {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.len() < other.len() {
Some(Ordering::Less)
} else if self.len() > other.len() {
Some(Ordering::Greater)
} else {
for (a, b) in self.iter().zip(other.iter()) {
if a.0 < b.0 || a.1 < b.1 {
return Some(Ordering::Less);
} else if a.0 > b.0 || a.1 > b.1 {
return Some(Ordering::Greater);
}
}
Some(Ordering::Equal)
}
}
}
impl PartialEq for Dict {
fn eq(&self, other: &Self) -> bool {
if self.id() == other.id() {
return true;
}
if let Some(Ordering::Equal) = self.partial_cmp(other) {
true
} else {
false
}
}
}
impl std::ops::Deref for Dict {
type Target = InnerDict;
fn deref(&self) -> &Self::Target {
&self.dict
}
}
impl std::ops::DerefMut for Dict {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.dict
}
}
impl From<Dict> for RefValue {
fn from(value: Dict) -> Self {
RefValue::from(Box::new(value) as BoxedObject)
}
}
#[test]
fn test_dict() {
assert_eq!(
crate::run("(b => 3, c => 1, a => 2)", ""),
Ok(Some(crate::value!(["b" => 3, "c" => 1, "a" => 2])))
);
}
#[test]
fn test_dict_compare() {
assert_eq!(
crate::run(
r#"
a = (a => 1, b => 2)
b = (b => 2, a => 1)
c = (a => 1, b => 2, c => 3)
d = (c => 3, b => 2, a => 1)
a < c a < b c < d a == a a != b c >= a b > a c > a c > b
"#,
""
),
Ok(Some(crate::value!([
true, true, true, true, true, true, true, true, true
])))
);
}
#[test]
fn test_dict_len() {
assert_eq!(
crate::run("dict().len() (a => 1, b => 2).len()", ""),
Ok(Some(crate::value!([(0 as usize), (2 as usize)])))
);
assert_eq!(
crate::run("dict_len(\"Donkey\")", ""),
Err("Line 1, column 1: dict_len() only accepts 'dict' as parameter, not 'str'".to_string())
)
}
#[test]
fn test_dict_update() {
assert_eq!(
crate::run("d = (a => 1, b => 2); d.update((c => 3)); d", ""),
Ok(Some(crate::value!(["a" => 1, "b" => 2, "c" => 3])))
)
}