use crate::{
scope::{LocalParamID, ScopePointer},
r#type::Type,
type_expr::{
ScopePortal, TypeExpr, TypeExprScope, TypeExprValidationError, Unscoped, node_signature::NodeSignature,
},
};
use petgraph::dot::Dot;
pub use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::prelude::StableDiGraph;
use std::{
collections::BTreeMap,
error::Error,
fmt::{self, Debug, Display},
};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "tsify")]
use tsify::Tsify;
pub mod inference;
pub mod validation;
mod private {
pub trait Sealed {}
}
#[derive(Debug, Clone)]
pub struct Edge {
pub source_port: usize,
pub target_port: usize,
}
#[derive(Debug, Clone)]
pub struct Node<T: Type, S: TypeExprScope = Unscoped> {
pub signature: NodeSignature<T, S>,
pub parent: Option<NodeIndex>,
pub type_hints: BTreeMap<LocalParamID, TypeExpr<T, S>>,
}
impl<T: Type> Node<T, Unscoped> {
pub fn new(signature: NodeSignature<T, Unscoped>) -> Self {
Self { signature, parent: None, type_hints: BTreeMap::new() }
}
pub fn new_child(signature: NodeSignature<T, Unscoped>, parent: NodeIndex) -> Self {
Self { signature, parent: Some(parent), type_hints: BTreeMap::new() }
}
pub fn with_type_hints(self, type_hints: BTreeMap<LocalParamID, TypeExpr<T, Unscoped>>) -> Self {
Self { type_hints, ..self }
}
}
pub trait IntoNode<T: Type>: private::Sealed {
fn into_node(self) -> Node<T, ScopePortal<T>>;
}
impl<T: Type> private::Sealed for Node<T, Unscoped> {}
impl<T: Type> private::Sealed for NodeSignature<T, Unscoped> {}
impl<T: Type> IntoNode<T> for NodeSignature<T, Unscoped> {
fn into_node(self) -> Node<T, ScopePortal<T>> {
Node { signature: self.into(), parent: None, type_hints: BTreeMap::new() }
}
}
impl<T: Type> IntoNode<T> for Node<T, Unscoped> {
fn into_node(self) -> Node<T, ScopePortal<T>> {
Node {
signature: self.signature.into(),
parent: self.parent,
type_hints: self.type_hints.into_iter().map(|(k, v)| (k, v.into())).collect(),
}
}
}
impl<T: Type> Default for Node<T, Unscoped> {
fn default() -> Self {
Node { signature: NodeSignature::default(), parent: None, type_hints: BTreeMap::new() }
}
}
impl<T: Type> From<Node<T, Unscoped>> for Node<T, ScopePortal<T>> {
fn from(value: Node<T, Unscoped>) -> Self {
Node {
signature: value.signature.into(),
parent: value.parent,
type_hints: value.type_hints.into_iter().map(|(k, v)| (k, v.into())).collect(),
}
}
}
impl<T: Type> From<NodeSignature<T, Unscoped>> for Node<T, ScopePortal<T>> {
fn from(sig: NodeSignature<T, Unscoped>) -> Self {
Node { signature: sig.into(), parent: None, type_hints: BTreeMap::new() }
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[cfg_attr(feature = "tsify", derive(Tsify))]
pub enum NodetyError {
NodeHasChildren,
ParentNotFound,
NodeNotFound,
CycleDetected,
TypeExprValidationError(TypeExprValidationError),
}
impl Display for NodetyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for NodetyError {}
type ChildrenMap = BTreeMap<NodeIndex, Vec<NodeIndex>>;
pub struct Nodety<T: Type> {
program: StableDiGraph<Node<T, ScopePortal<T>>, Edge>,
children: ChildrenMap,
}
impl<T: Type> Default for Nodety<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Type> Nodety<T> {
pub fn new() -> Self {
Self::with_capacity(0, 0)
}
pub fn with_capacity(nodes: usize, edges: usize) -> Self {
Self { program: StableDiGraph::with_capacity(nodes, edges), children: BTreeMap::new() }
}
pub fn add_node(&mut self, node: impl IntoNode<T>) -> Result<NodeIndex, NodetyError> {
let node: Node<T, ScopePortal<T>> = node.into_node();
let node_scope = if let Some(parent) = node.parent {
if !self.program.contains_node(parent) {
return Err(NodetyError::ParentNotFound);
}
self.build_node_scope(parent)
} else {
ScopePointer::new_root()
};
node.signature.validate(&node_scope).map_err(NodetyError::TypeExprValidationError)?;
let parent = node.parent;
let node_idx = self.program.add_node(node);
if let Some(parent) = parent {
self.register_child(parent, node_idx);
}
Ok(node_idx)
}
pub fn update_node(&mut self, node_id: NodeIndex, node: impl IntoNode<T>) -> Result<(), NodetyError> {
let node: Node<T, ScopePortal<T>> = node.into_node();
if !self.program.contains_node(node_id) {
return Err(NodetyError::NodeNotFound);
}
let node_scope = if let Some(parent) = self.program[node_id].parent {
self.build_node_scope(parent)
} else {
ScopePointer::new_root()
};
node.signature.validate(&node_scope).map_err(NodetyError::TypeExprValidationError)?;
if let Some(new_parent) = node.parent {
if !self.program.contains_node(new_parent) {
return Err(NodetyError::ParentNotFound);
}
if self.would_create_cycle(node_id, new_parent) {
return Err(NodetyError::CycleDetected);
}
}
if let Some(old_parent) = self.program[node_id].parent {
self.unregister_child(old_parent, node_id);
}
if let Some(new_parent) = node.parent {
self.register_child(new_parent, node_id);
}
self.program[node_id] = node;
Ok(())
}
pub fn remove_node(&mut self, node_id: NodeIndex) -> Result<(), NodetyError> {
if self.children.get(&node_id).is_some_and(|c| !c.is_empty()) {
return Err(NodetyError::NodeHasChildren);
}
if let Some(old_node) = self.program.remove_node(node_id) {
if let Some(parent) = old_node.parent {
self.unregister_child(parent, node_id);
}
}
self.children.remove(&node_id);
Ok(())
}
pub fn add_edge(
&mut self,
source: NodeIndex,
target: NodeIndex,
source_port: usize,
target_port: usize,
) -> EdgeIndex {
self.program.add_edge(source, target, Edge { source_port, target_port })
}
pub fn remove_edge(&mut self, edge_idx: EdgeIndex) -> Option<Edge> {
self.program.remove_edge(edge_idx)
}
pub fn get_node(&self, node_idx: NodeIndex) -> Option<&Node<T, ScopePortal<T>>> {
self.program.node_weight(node_idx)
}
pub fn program(&self) -> &StableDiGraph<Node<T, ScopePortal<T>>, Edge> {
&self.program
}
fn unregister_child(&mut self, parent: NodeIndex, child_id: NodeIndex) {
if let Some(children) = self.children.get_mut(&parent) {
children.retain(|&id| id != child_id);
if children.is_empty() {
self.children.remove(&parent);
}
}
}
fn register_child(&mut self, parent: NodeIndex, child_id: NodeIndex) {
self.children.entry(parent).or_default().push(child_id);
}
fn would_create_cycle(&self, node_id: NodeIndex, new_parent: NodeIndex) -> bool {
let mut current = new_parent;
loop {
if current == node_id {
return true;
}
let Some(node) = self.program.node_weight(current) else {
return false;
};
let Some(parent) = node.parent else {
return false;
};
current = parent;
}
}
pub fn to_dot(&self) -> String {
format!("{:?}", Dot::new(&self.program))
}
}