1use 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
60pub 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 for check in kind_checks {
114 let Some(ix) = graph.index_of(&check.node) else {
115 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}