use std::{error::Error, fmt, mem::size_of};
use num_bigint::BigInt;
use crate::{
builtins::BuiltinsFunctions,
exceptions::ExcType,
object::{MontyDate, MontyDateTime, MontyFileHandle, MontyTime, MontyTimeDelta, MontyTimeZone, MontyType},
uuid::MontyUuid,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
pub struct NodeId(pub u32);
impl NodeId {
#[must_use]
pub fn index(self) -> usize {
self.0 as usize
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum MontyNode {
Ellipsis,
NotImplemented,
None,
Bool(bool),
Int(i64),
BigInt(BigInt),
Float(f64),
String(String),
Bytes(#[serde(with = "serde_bytes")] Vec<u8>),
Date(MontyDate),
DateTime(MontyDateTime),
Time(MontyTime),
TimeDelta(MontyTimeDelta),
TimeZone(MontyTimeZone),
Exception {
exc_type: ExcType,
arg: Option<String>,
},
Type(MontyType),
BuiltinFunction(BuiltinsFunctions),
Path(String),
FileHandle(MontyFileHandle),
Function {
name: String,
docstring: Option<String>,
},
Repr(String),
Cycle(String),
List(Vec<NodeId>),
Tuple(Vec<NodeId>),
Set(Vec<NodeId>),
FrozenSet(Vec<NodeId>),
NamedTuple {
type_name: String,
field_names: Vec<String>,
values: Vec<NodeId>,
},
Dict(Vec<(NodeId, NodeId)>),
ClassType(Box<ClassTypeNode>),
ClassInstance {
class_type: NodeId,
instance_id: MontyUuid,
attrs: Vec<(NodeId, NodeId)>,
},
}
impl MontyNode {
pub fn for_each_child(&self, mut f: impl FnMut(NodeId)) {
match self {
Self::List(ids) | Self::Tuple(ids) | Self::Set(ids) | Self::FrozenSet(ids) => {
ids.iter().copied().for_each(f);
}
Self::NamedTuple { values, .. } => values.iter().copied().for_each(f),
Self::Dict(pairs) => pairs.iter().for_each(|(key, value)| {
f(*key);
f(*value);
}),
Self::ClassType(class) => class.attrs.iter().for_each(|(key, value)| {
f(*key);
f(*value);
}),
Self::ClassInstance { class_type, attrs, .. } => {
f(*class_type);
for (key, value) in attrs {
f(*key);
f(*value);
}
}
_ => {}
}
}
pub fn for_each_child_mut(&mut self, mut f: impl FnMut(&mut NodeId)) {
match self {
Self::List(ids) | Self::Tuple(ids) | Self::Set(ids) | Self::FrozenSet(ids) => ids.iter_mut().for_each(f),
Self::NamedTuple { values, .. } => values.iter_mut().for_each(f),
Self::Dict(pairs) => pairs.iter_mut().for_each(|(key, value)| {
f(key);
f(value);
}),
Self::ClassType(class) => class.attrs.iter_mut().for_each(|(key, value)| {
f(key);
f(value);
}),
Self::ClassInstance { class_type, attrs, .. } => {
f(class_type);
for (key, value) in attrs {
f(key);
f(value);
}
}
_ => {}
}
}
#[must_use]
pub fn cycle_placeholder(&self) -> &'static str {
match self {
Self::List(_) => "[...]",
Self::Tuple(_) | Self::NamedTuple { .. } => "(...)",
Self::Dict(_) => "{...}",
_ => "...",
}
}
#[must_use]
pub fn is_leaf(&self) -> bool {
let mut leaf = true;
self.for_each_child(|_| leaf = false);
leaf
}
#[must_use]
pub const fn metadata_string_size(value: &str) -> usize {
size_of::<String>().saturating_add(value.len())
}
#[must_use]
pub fn decoded_size(&self) -> usize {
let name_len = |name: &Option<String>| -> usize { name.as_ref().map_or(0, String::len) };
let ids = |ids: &[NodeId]| ids.len().saturating_mul(size_of::<NodeId>());
let pairs = |pairs: &[(NodeId, NodeId)]| pairs.len().saturating_mul(2 * size_of::<NodeId>());
let payload = match self {
Self::String(s) | Self::Path(s) | Self::Repr(s) | Self::Cycle(s) => s.len(),
Self::Bytes(b) => b.len(),
Self::BigInt(bi) => usize::try_from(bi.bits().div_ceil(8)).unwrap_or(usize::MAX),
Self::Exception { arg, .. } => name_len(arg),
Self::FileHandle(fh) => fh.path.len(),
Self::Function { name, docstring } => name.len().saturating_add(name_len(docstring)),
Self::DateTime(dt) => name_len(&dt.timezone_name),
Self::Time(t) => name_len(&t.timezone_name),
Self::TimeZone(tz) => name_len(&tz.name),
Self::List(items) | Self::Tuple(items) | Self::Set(items) | Self::FrozenSet(items) => ids(items),
Self::NamedTuple {
type_name,
field_names,
values,
} => {
let names: usize = field_names.iter().map(|name| Self::metadata_string_size(name)).sum();
type_name.len().saturating_add(names).saturating_add(ids(values))
}
Self::Dict(entries) => pairs(entries),
Self::ClassType(class) => size_of::<ClassTypeNode>()
.saturating_add(class.name.len())
.saturating_add(pairs(&class.attrs)),
Self::ClassInstance { attrs, .. } => pairs(attrs),
_ => 0,
};
size_of::<Self>().saturating_add(payload)
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ClassTypeNode {
pub name: String,
pub id: MontyUuid,
pub host_defined: bool,
pub is_dataclass: bool,
pub attrs: Vec<(NodeId, NodeId)>,
}
impl PartialEq for MontyNode {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Ellipsis, Self::Ellipsis)
| (Self::NotImplemented, Self::NotImplemented)
| (Self::None, Self::None) => true,
(Self::Bool(a), Self::Bool(b)) => a == b,
(Self::Int(a), Self::Int(b)) => a == b,
(Self::BigInt(a), Self::BigInt(b)) => a == b,
(Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
(Self::String(a), Self::String(b))
| (Self::Path(a), Self::Path(b))
| (Self::Repr(a), Self::Repr(b))
| (Self::Cycle(a), Self::Cycle(b)) => a == b,
(Self::Bytes(a), Self::Bytes(b)) => a == b,
(Self::Date(a), Self::Date(b)) => a == b,
(Self::DateTime(a), Self::DateTime(b)) => a == b,
(Self::Time(a), Self::Time(b)) => a == b,
(Self::TimeDelta(a), Self::TimeDelta(b)) => a == b,
(Self::TimeZone(a), Self::TimeZone(b)) => a == b,
(
Self::Exception {
exc_type: a_type,
arg: a_arg,
},
Self::Exception {
exc_type: b_type,
arg: b_arg,
},
) => a_type == b_type && a_arg == b_arg,
(Self::Type(a), Self::Type(b)) => a == b,
(Self::BuiltinFunction(a), Self::BuiltinFunction(b)) => a == b,
(Self::FileHandle(a), Self::FileHandle(b)) => {
a.path == b.path && a.mode == b.mode && a.position == b.position
}
(
Self::Function {
name: a_name,
docstring: a_doc,
},
Self::Function {
name: b_name,
docstring: b_doc,
},
) => a_name == b_name && a_doc == b_doc,
(Self::List(a), Self::List(b))
| (Self::Tuple(a), Self::Tuple(b))
| (Self::Set(a), Self::Set(b))
| (Self::FrozenSet(a), Self::FrozenSet(b)) => a == b,
(
Self::NamedTuple {
type_name: a_name,
field_names: a_fields,
values: a_values,
},
Self::NamedTuple {
type_name: b_name,
field_names: b_fields,
values: b_values,
},
) => a_name == b_name && a_fields == b_fields && a_values == b_values,
(Self::Dict(a), Self::Dict(b)) => a == b,
(Self::ClassType(a), Self::ClassType(b)) => a == b,
(
Self::ClassInstance {
class_type: a_class,
instance_id: a_id,
attrs: a_attrs,
},
Self::ClassInstance {
class_type: b_class,
instance_id: b_id,
attrs: b_attrs,
},
) => a_class == b_class && a_id == b_id && a_attrs == b_attrs,
_ => false,
}
}
}
impl Eq for MontyNode {}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MontyGraph {
nodes: Vec<MontyNode>,
}
impl MontyGraph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
nodes: Vec::with_capacity(capacity),
}
}
pub fn from_nodes(nodes: Vec<MontyNode>) -> Result<Self, GraphError> {
Self::validate(&nodes)?;
Ok(Self { nodes })
}
pub fn validate(nodes: &[MontyNode]) -> Result<(), GraphError> {
nodes
.iter()
.enumerate()
.try_for_each(|(index, node)| Self::check_node(nodes, index, node))
}
pub fn push(&mut self, node: MontyNode) -> NodeId {
let index = self.nodes.len();
if let Err(err) = Self::check_node(&self.nodes, index, &node) {
panic!("invalid node pushed onto a MontyGraph: {err}");
}
self.nodes.push(node);
Self::id_at(index)
}
pub fn merge(&mut self, other: Self) -> u32 {
let offset = Self::id_at(self.nodes.len()).0;
self.nodes.reserve(other.nodes.len());
for mut node in other.nodes {
node.for_each_child_mut(|id| id.0 += offset);
self.nodes.push(node);
}
offset
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[must_use]
pub fn node(&self, id: NodeId) -> &MontyNode {
&self.nodes[id.index()]
}
pub fn node_mut(&mut self, id: NodeId) -> &mut MontyNode {
&mut self.nodes[id.index()]
}
pub fn nodes_mut(&mut self) -> &mut [MontyNode] {
&mut self.nodes
}
#[must_use]
pub fn nodes(&self) -> &[MontyNode] {
&self.nodes
}
#[must_use]
pub fn into_nodes(self) -> Vec<MontyNode> {
self.nodes
}
pub fn check_root(&self, root: NodeId) -> Result<(), GraphError> {
if root.index() < self.nodes.len() {
Ok(())
} else {
Err(GraphError::RootOutOfRange {
root,
len: self.nodes.len(),
})
}
}
#[must_use]
pub fn type_name(&self, id: NodeId) -> &str {
match self.node(id) {
MontyNode::None => "NoneType",
MontyNode::Ellipsis => "ellipsis",
MontyNode::NotImplemented => "NotImplementedType",
MontyNode::Bool(_) => "bool",
MontyNode::Int(_) | MontyNode::BigInt(_) => "int",
MontyNode::Float(_) => "float",
MontyNode::String(_) => "str",
MontyNode::Bytes(_) => "bytes",
MontyNode::List(_) => "list",
MontyNode::Tuple(_) => "tuple",
MontyNode::NamedTuple { .. } => "namedtuple",
MontyNode::Dict(_) => "dict",
MontyNode::Set(_) => "set",
MontyNode::FrozenSet(_) => "frozenset",
MontyNode::Date(_) => "date",
MontyNode::DateTime(_) => "datetime",
MontyNode::Time(_) => "time",
MontyNode::TimeDelta(_) => "timedelta",
MontyNode::TimeZone(_) => "timezone",
MontyNode::Exception { .. } => "Exception",
MontyNode::Path(_) => "PosixPath",
MontyNode::FileHandle(handle) => handle.mode.type_name(),
MontyNode::ClassInstance { class_type, .. } => match self.node(*class_type) {
MontyNode::ClassType(class) => &class.name,
_ => "object",
},
MontyNode::Type(_) | MontyNode::ClassType(_) => "type",
MontyNode::BuiltinFunction(_) => "builtin_function_or_method",
MontyNode::Function { .. } => "function",
MontyNode::Repr(_) => "repr",
MontyNode::Cycle(_) => "cycle",
}
}
#[must_use]
pub fn decoded_size(&self) -> usize {
self.nodes
.iter()
.fold(0usize, |size, node| size.saturating_add(node.decoded_size()))
}
fn check_node(nodes: &[MontyNode], index: usize, node: &MontyNode) -> Result<(), GraphError> {
let holder = Self::id_at(index);
let mut result = Ok(());
node.for_each_child(|child| {
if result.is_ok() && child.index() >= index {
result = Err(GraphError::IndexNotLower { node: holder, child });
}
});
result?;
match node {
MontyNode::ClassInstance { class_type, .. }
if !matches!(nodes[class_type.index()], MontyNode::ClassType(_)) =>
{
Err(GraphError::ClassTypeNotAClass { node: holder })
}
_ => Ok(()),
}
}
fn id_at(index: usize) -> NodeId {
NodeId(u32::try_from(index).expect("MontyGraph exceeds u32::MAX nodes"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GraphError {
IndexNotLower {
node: NodeId,
child: NodeId,
},
ClassTypeNotAClass {
node: NodeId,
},
RootOutOfRange {
root: NodeId,
len: usize,
},
}
impl fmt::Display for GraphError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IndexNotLower { node, child } => {
write!(f, "value node {node} references node {child}, which is not below it")
}
Self::ClassTypeNotAClass { node } => write!(f, "class instance node {node} does not point at a class type"),
Self::RootOutOfRange { root, len } => {
write!(f, "value root {root} is out of range for an arena of {len} nodes")
}
}
}
}
impl Error for GraphError {}