openquiver-graph 0.1.0

OpenQuiver graph IR: nodes, pins, types. Bottom layer, no internal dependencies.
Documentation
//! The graph itself.

use std::collections::{BTreeMap, BTreeSet};

use serde::{Deserialize, Serialize};

use crate::{Edge, Node, NodeName, PinRef};

/// A graph: nodes joined by exec and data wires.
///
/// Nodes live in an ordered map rather than a vector because the canonical
/// order is alphabetical by name. That order is what keeps diffs stable and
/// spreads concurrent insertions by different people across the file instead
/// of crowding them at the end.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Graph {
    name: String,
    pub(crate) nodes: BTreeMap<NodeName, Node>,
    pub(crate) edges: BTreeSet<Edge>,
}

impl Graph {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            nodes: BTreeMap::new(),
            edges: BTreeSet::new(),
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn insert_node(&mut self, node: Node) {
        self.nodes.insert(node.name.clone(), node);
    }

    pub fn node(&self, name: &str) -> Option<&Node> {
        // ASCII is already normalized, so the overwhelmingly common case is a
        // borrowed probe with no allocation. Only a name that might decompose
        // pays for normalizing.
        if name.is_ascii() {
            return self.nodes.get(name);
        }
        self.nodes.get(&NodeName::new(name))
    }

    /// Nodes in canonical order, by name.
    pub fn nodes(&self) -> impl Iterator<Item = &Node> {
        self.nodes.values()
    }

    /// A set rather than a vector: the same wire drawn twice, including by two
    /// people on separate branches, stays a single edge.
    pub fn insert_edge(&mut self, edge: Edge) {
        self.edges.insert(edge);
    }

    /// Edges in canonical order: exec first, then data, each sorted by its pin
    /// pair.
    pub fn edges(&self) -> impl Iterator<Item = &Edge> {
        self.edges.iter()
    }

    /// Wires leaving a pin.
    ///
    /// The editor needs this for Blueprint's rule that connecting a second wire
    /// to an exec output replaces the first, and the compiler needs it to check
    /// pin cardinality at all.
    pub fn edges_from(&self, pin: &PinRef) -> impl Iterator<Item = &Edge> {
        self.edges.iter().filter(move |e| e.from() == pin)
    }

    /// Wires arriving at a pin.
    pub fn edges_into(&self, pin: &PinRef) -> impl Iterator<Item = &Edge> {
        self.edges.iter().filter(move |e| e.to() == pin)
    }
}