Skip to main content

ezu_graph/
build.rs

1//! Drive [`GraphBuilder`] from a parsed [`spec::Document`] using a
2//! [`NodeRegistry`].
3
4use ezu_style as spec;
5
6use crate::graph::{BuildError, Graph, GraphBuilder};
7use crate::port::PortKind;
8use crate::registry::{FactoryCtx, FactoryError, NodeRegistry};
9
10#[derive(Debug, thiserror::Error)]
11pub enum BuildGraphError {
12    #[error("unknown op `{op}` on node `{node}`")]
13    UnknownOp { node: String, op: String },
14
15    #[error("factory error on node `{node}`: {source}")]
16    Factory {
17        node: String,
18        #[source]
19        source: FactoryError,
20    },
21
22    #[error(transparent)]
23    Expand(#[from] spec::ExpandError),
24
25    #[error(
26        "call `{call}` of `{func}`: input `{input}` expects {expected}, but `@{src}` produces {got}"
27    )]
28    FuncInputKind {
29        call: String,
30        func: String,
31        input: String,
32        expected: PortKind,
33        src: String,
34        got: PortKind,
35    },
36
37    #[error("call `{call}` of `{func}`: declared output-kind is {declared}, but the body produces {got}")]
38    FuncOutputKind {
39        call: String,
40        func: String,
41        declared: PortKind,
42        got: PortKind,
43    },
44
45    #[error(transparent)]
46    Graph(#[from] BuildError),
47}
48
49fn port_kind(k: spec::FuncKind) -> PortKind {
50    match k {
51        spec::FuncKind::Features => PortKind::Features,
52        spec::FuncKind::Raster => PortKind::Raster,
53        spec::FuncKind::Sprite => PortKind::Sprite,
54        spec::FuncKind::Brush => PortKind::Brush,
55        spec::FuncKind::Scalar => PortKind::Scalar,
56        spec::FuncKind::ScalarField => PortKind::ScalarField,
57    }
58}
59
60/// Build a typed [`Graph`] from a parsed document and a registry of
61/// node factories. Documents with a `functions` block are expanded
62/// inline first; declared input/output kinds are verified against the
63/// built graph's resolved port kinds.
64pub fn build_graph(
65    doc: &spec::Document,
66    registry: &NodeRegistry,
67) -> Result<Graph, BuildGraphError> {
68    let expanded = spec::expand_functions(doc)?;
69    let (doc, kind_checks) = match &expanded {
70        Some(e) => (&e.doc, e.kind_checks.as_slice()),
71        None => (doc, &[][..]),
72    };
73
74    let ctx = FactoryCtx {
75        params: &doc.params,
76        sources: &doc.sources,
77    };
78
79    let mut gb = GraphBuilder::new();
80    let mut pending: Vec<(String, Vec<crate::registry::Connection>)> = Vec::new();
81
82    for (id, spec) in &doc.nodes {
83        let factory = registry
84            .get(&spec.op)
85            .ok_or_else(|| BuildGraphError::UnknownOp {
86                node: id.clone(),
87                op: spec.op.clone(),
88            })?;
89
90        let built = factory
91            .build(&spec.fields, &ctx)
92            .map_err(|e| BuildGraphError::Factory {
93                node: id.clone(),
94                source: e,
95            })?;
96
97        gb.add_node(id.clone(), built.node);
98        pending.push((id.clone(), built.connections));
99    }
100
101    for (dst, conns) in pending {
102        for c in conns {
103            gb.connect(c.src, dst.clone(), c.port);
104        }
105    }
106
107    gb.set_output(doc.output.as_str().to_string());
108    let graph = gb.build()?;
109
110    // Verify each call site's declared kinds against the resolved port
111    // kinds. Argument sources and the call's output node are plain
112    // graph nodes after expansion, so this is a pure lookup.
113    for check in kind_checks {
114        let Some(ix) = graph.index_of(&check.node) else {
115            // The referenced node failed to resolve — the builder has
116            // already reported the real error path; skip.
117            continue;
118        };
119        let got = graph.output_kind(ix);
120        let expected = port_kind(check.declared);
121        if got != expected {
122            return Err(match &check.input {
123                Some(input) => BuildGraphError::FuncInputKind {
124                    call: check.call.clone(),
125                    func: check.func.clone(),
126                    input: input.clone(),
127                    expected,
128                    src: check.node.clone(),
129                    got,
130                },
131                None => BuildGraphError::FuncOutputKind {
132                    call: check.call.clone(),
133                    func: check.func.clone(),
134                    declared: expected,
135                    got,
136                },
137            });
138        }
139    }
140    Ok(graph)
141}