use std::collections::HashMap;
use monty_types::{
BuiltinsFunctions, MontyException, MontyObject,
unstable::{self, ClassTypeNode, MontyGraph, MontyNode, NodeId},
};
use pyo3::{
prelude::*,
types::{PyBool, PyBytes, PyDate, PyDelta, PyDict, PyFrozenSet, PyList, PySet, PyString, PyTuple},
};
use super::{
class_instance::{ClassHeader, InstanceStore, PyMontyClassProxy, PyMontyClassTypeProxy},
convert::{
PyMontyFileHandle, get_namedtuple, get_pure_posix_path, host_type_object, import_builtins,
monty_datetime_to_py, monty_time_to_py, monty_timezone_to_py,
},
exceptions::exc_monty_to_py,
std_type_proxy::{PyMontyStdTypeProxy, StdTypeRef},
};
pub fn monty_to_py(py: Python<'_>, value: &MontyObject, store: &InstanceStore) -> PyResult<Py<PyAny>> {
let (graph, root) = unstable::graph_parts(value);
Ok(DecodedArena::new(py, graph, store)?.get(py, root))
}
pub struct DecodedArena {
built: Vec<Py<PyAny>>,
}
impl DecodedArena {
pub fn new(py: Python<'_>, graph: &MontyGraph, store: &InstanceStore) -> PyResult<Self> {
let mut decoder = Decoder {
py,
graph,
store,
built: Vec::with_capacity(graph.len()),
namedtuple_types: HashMap::new(),
};
for node in graph.nodes() {
let obj = decoder.decode(node)?;
decoder.built.push(obj);
}
Ok(Self { built: decoder.built })
}
#[must_use]
pub fn get(&self, py: Python<'_>, id: NodeId) -> Py<PyAny> {
self.built[id.index()].clone_ref(py)
}
}
struct Decoder<'a, 'py> {
py: Python<'py>,
graph: &'a MontyGraph,
store: &'a InstanceStore,
built: Vec<Py<PyAny>>,
namedtuple_types: HashMap<(String, Vec<String>), Py<PyAny>>,
}
impl Decoder<'_, '_> {
fn decode(&mut self, node: &MontyNode) -> PyResult<Py<PyAny>> {
let py = self.py;
match node {
MontyNode::None => Ok(py.None()),
MontyNode::Ellipsis => Ok(py.Ellipsis()),
MontyNode::NotImplemented => Ok(import_builtins(py)?.getattr(py, "NotImplemented")?),
MontyNode::Bool(b) => Ok(PyBool::new(py, *b).to_owned().into_any().unbind()),
MontyNode::Int(i) => Ok(i.into_pyobject(py)?.clone().into_any().unbind()),
MontyNode::BigInt(bi) => Ok(bi.into_pyobject(py)?.clone().into_any().unbind()),
MontyNode::Float(f) => Ok(f.into_pyobject(py)?.clone().into_any().unbind()),
MontyNode::String(s) => Ok(PyString::new(py, s).into_any().unbind()),
MontyNode::Bytes(b) => Ok(PyBytes::new(py, b).into_any().unbind()),
MontyNode::List(items) => Ok(PyList::new(py, self.children(items))?.into_any().unbind()),
MontyNode::Tuple(items) => Ok(PyTuple::new(py, self.children(items))?.into_any().unbind()),
MontyNode::NamedTuple {
type_name,
field_names,
values,
} => {
let nt_type = self.namedtuple_type(type_name, field_names)?;
let instance = nt_type.bind(py).call_method1("_make", (self.children(values),))?;
Ok(instance.into_any().unbind())
}
MontyNode::Dict(pairs) => {
let dict = PyDict::new(py);
for (key, value) in pairs {
dict.set_item(self.child(*key), self.child(*value))?;
}
Ok(dict.into_any().unbind())
}
MontyNode::Set(items) => {
let set = PySet::empty(py)?;
for item in items {
set.add(self.child(*item))?;
}
Ok(set.into_any().unbind())
}
MontyNode::FrozenSet(items) => Ok(PyFrozenSet::new(py, self.children(items))?.into_any().unbind()),
MontyNode::Exception { exc_type, arg } => {
let exc = exc_monty_to_py(py, MontyException::new(*exc_type, arg.clone()));
Ok(exc.into_value(py).into_any())
}
MontyNode::Date(date) => PyDate::new(py, date.year, date.month, date.day)
.map(Bound::into_any)
.map(Bound::unbind),
MontyNode::DateTime(datetime) => monty_datetime_to_py(py, datetime),
MontyNode::Time(time) => monty_time_to_py(py, time),
MontyNode::TimeDelta(delta) => PyDelta::new(py, delta.days, delta.seconds, delta.microseconds, true)
.map(Bound::into_any)
.map(Bound::unbind),
MontyNode::TimeZone(timezone) => monty_timezone_to_py(py, timezone),
MontyNode::Type(t) => match host_type_object(py, *t)? {
Some(ty) => Ok(ty),
None => std_type_proxy(py, StdTypeRef::Type(*t)),
},
MontyNode::BuiltinFunction(BuiltinsFunctions::Type) => import_builtins(py)?.getattr(py, "type"),
MontyNode::BuiltinFunction(f) => std_type_proxy(py, StdTypeRef::Function(*f)),
MontyNode::ClassType(class) => {
if let Some(class) = self.store.get_class(py, &class.id)? {
Ok(class)
} else {
let proxy = PyMontyClassTypeProxy {
class_type: class_header(class),
attributes: self.attrs_dict(&class.attrs)?,
};
Ok(Py::new(py, proxy)?.into_any())
}
}
MontyNode::ClassInstance {
class_type,
instance_id,
attrs,
} => {
if let Some(wrapper) = self.store.get(py, instance_id)? {
wrapper.bind(py).getattr("value").map(Bound::unbind)
} else {
let MontyNode::ClassType(class) = self.graph.node(*class_type) else {
unreachable!("the arena's validation makes class_type a ClassType node")
};
let proxy = PyMontyClassProxy {
class_type: class_header(class),
instance_id: *instance_id,
attributes: self.attrs_dict(attrs)?,
class_attributes: self.attrs_dict(&class.attrs)?,
};
Ok(Py::new(py, proxy)?.into_any())
}
}
MontyNode::Path(p) => Ok(get_pure_posix_path(py)?.call1((p,))?.into_any().unbind()),
MontyNode::FileHandle(handle) => Ok(Py::new(py, PyMontyFileHandle::from_inner(handle.clone()))?.into_any()),
MontyNode::Repr(s) | MontyNode::Cycle(s) => Ok(PyString::new(py, s).into_any().unbind()),
MontyNode::Function { name, .. } => Ok(PyString::new(py, name).into_any().unbind()),
}
}
fn child(&self, id: NodeId) -> Py<PyAny> {
self.built[id.index()].clone_ref(self.py)
}
fn children(&self, ids: &[NodeId]) -> Vec<Py<PyAny>> {
ids.iter().map(|id| self.child(*id)).collect()
}
fn attrs_dict(&self, attrs: &[(NodeId, NodeId)]) -> PyResult<Py<PyDict>> {
let dict = PyDict::new(self.py);
for (key, value) in attrs {
if let MontyNode::String(name) = self.graph.node(*key) {
dict.set_item(name, self.child(*value))?;
}
}
Ok(dict.unbind())
}
fn namedtuple_type(&mut self, type_name: &str, field_names: &[String]) -> PyResult<Py<PyAny>> {
let py = self.py;
let key = (type_name.to_owned(), field_names.to_vec());
if let Some(nt_type) = self.namedtuple_types.get(&key) {
return Ok(nt_type.clone_ref(py));
}
let (module, simple_name) = match type_name.rfind('.') {
Some(idx) => (&type_name[..idx], &type_name[idx + 1..]),
None => ("", type_name),
};
let namedtuple_fn = get_namedtuple(py)?;
let py_field_names = PyList::new(py, field_names)?;
let nt_type = if module.is_empty() {
namedtuple_fn.call1((simple_name, py_field_names))?
} else {
let kwargs = PyDict::new(py);
kwargs.set_item("module", module)?;
namedtuple_fn.call((simple_name, py_field_names), Some(&kwargs))?
}
.unbind();
self.namedtuple_types.insert(key, nt_type.clone_ref(py));
Ok(nt_type)
}
}
fn std_type_proxy(py: Python<'_>, inner: StdTypeRef) -> PyResult<Py<PyAny>> {
Ok(Py::new(py, PyMontyStdTypeProxy { inner })?.into_any())
}
fn class_header(class: &ClassTypeNode) -> ClassHeader {
ClassHeader {
name: class.name.clone(),
id: class.id,
host_defined: class.host_defined,
is_dataclass: class.is_dataclass,
}
}