use std::any::Any;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
use crate::error::Error;
use crate::error::ErrorCode;
use crate::value::HashableValue;
use crate::value::Shared;
use crate::value::SharedFrozen;
use crate::value::Value;
pub struct ReduceResult {
pub module: String,
pub class: String,
pub args: Value,
pub state: Option<Value>,
pub list_items: Option<Vec<Value>>,
pub dict_items: Option<Vec<(HashableValue, Value)>>,
}
impl ReduceResult {
pub fn state_or_none(&self) -> Value {
self.state.clone().unwrap_or(Value::None)
}
}
pub trait PickleObject: fmt::Debug + fmt::Display {
fn __setstate__(&mut self, _state: Value) {}
fn __setitem__(&mut self, _key: Value, _value: Value) {}
fn __hash__(&self) -> Result<HashableValue, Error> {
Err(Error::Syntax(ErrorCode::ValueNotHashable))
}
fn class_info(&self) -> (&str, &str);
fn __reduce__(&self) -> ReduceResult;
fn eq_dyn(&self, other: &dyn PickleObject) -> bool;
fn cmp_dyn(&self, other: &dyn PickleObject) -> Ordering;
fn clone_dyn(&self) -> Box<dyn PickleObject>;
fn as_any(&self) -> &dyn Any;
}
#[derive(Debug)]
pub struct ObjectConstructionInfo<'a> {
pub module: &'a str,
pub class: &'a str,
}
pub type ObjectFactory = Box<dyn Fn(ObjectConstructionInfo<'_>) -> Option<Box<dyn PickleObject>>>;
#[derive(Clone, Debug)]
pub struct DictObject {
module: String,
class: String,
state: BTreeMap<HashableValue, Value>,
}
impl DictObject {
pub fn new(module: String, class: String) -> Self {
DictObject {
module,
class,
state: BTreeMap::new(),
}
}
pub fn state(&self) -> &BTreeMap<HashableValue, Value> {
&self.state
}
pub fn into_value(self) -> Value {
Value::Dict(Shared::new(self.state))
}
}
impl fmt::Display for DictObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<{}.{} {{", self.module, self.class)?;
for (i, (key, value)) in self.state.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{key}: {value}")?;
}
write!(f, "}}>")
}
}
impl PickleObject for DictObject {
fn __setstate__(&mut self, state: Value) {
let (dict_state, slot_state) = match &state {
Value::Tuple(t) if t.inner().len() == 2 => {
let inner = t.inner();
(inner[0].clone(), Some(inner[1].clone()))
}
_ => (state, None),
};
match dict_state {
Value::Dict(d) => {
let d = d.inner();
self.state
.extend(d.iter().map(|(k, v)| (k.clone(), v.clone())));
}
Value::None => {} other => {
self.state.insert(
HashableValue::String(SharedFrozen::new("__state__".into())),
other,
);
}
}
if let Some(Value::Dict(d)) = slot_state {
let d = d.inner();
self.state
.extend(d.iter().map(|(k, v)| (k.clone(), v.clone())));
}
}
fn __setitem__(&mut self, key: Value, value: Value) {
if let Ok(hk) = key.into_hashable() {
self.state.insert(hk, value);
}
}
fn __hash__(&self) -> Result<HashableValue, Error> {
let items: std::collections::BTreeSet<HashableValue> = self
.state
.iter()
.map(|(k, v)| {
let hv = v.clone().into_hashable()?;
Ok(HashableValue::Tuple(SharedFrozen::new(vec![k.clone(), hv])))
})
.collect::<Result<_, Error>>()?;
Ok(HashableValue::FrozenSet(SharedFrozen::new(items)))
}
fn class_info(&self) -> (&str, &str) {
(&self.module, &self.class)
}
fn __reduce__(&self) -> ReduceResult {
ReduceResult {
module: self.module.clone(),
class: self.class.clone(),
args: Value::Tuple(SharedFrozen::new(vec![])),
state: Some(Value::Dict(Shared::new(self.state.clone()))),
list_items: None,
dict_items: None,
}
}
fn eq_dyn(&self, other: &dyn PickleObject) -> bool {
match other.as_any().downcast_ref::<Self>() {
Some(other) => {
self.module == other.module
&& self.class == other.class
&& self.state == other.state
}
None => false,
}
}
fn cmp_dyn(&self, other: &dyn PickleObject) -> Ordering {
match other.as_any().downcast_ref::<Self>() {
Some(other) => self.class_info().cmp(&other.class_info()).then_with(|| {
self.state
.keys()
.cmp(other.state.keys())
.then_with(|| self.state.len().cmp(&other.state.len()))
}),
None => self.class_info().cmp(&other.class_info()),
}
}
fn clone_dyn(&self) -> Box<dyn PickleObject> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}