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("legend entry `{label}` names `@{src}`, which is not a node in this style")]
46 LegendUnknownNode { label: String, src: String },
47
48 #[error("legend entry `{label}` names `@{src}`, which produces {got} — a legend entry must name a node that draws something ({expected})")]
49 LegendNodeKind {
50 label: String,
51 src: String,
52 expected: PortKind,
53 got: PortKind,
54 },
55
56 #[error(transparent)]
57 Graph(#[from] BuildError),
58}
59
60fn port_kind(k: spec::FuncKind) -> PortKind {
61 match k {
62 spec::FuncKind::Features => PortKind::Features,
63 spec::FuncKind::Raster => PortKind::Raster,
64 spec::FuncKind::Sprite => PortKind::Sprite,
65 spec::FuncKind::Brush => PortKind::Brush,
66 spec::FuncKind::Scalar => PortKind::Scalar,
67 spec::FuncKind::ScalarField => PortKind::ScalarField,
68 }
69}
70
71pub fn build_graph(
76 doc: &spec::Document,
77 registry: &NodeRegistry,
78) -> Result<Graph, BuildGraphError> {
79 let expanded = spec::expand_functions(doc)?;
80 let (doc, kind_checks) = match &expanded {
81 Some(e) => (&e.doc, e.kind_checks.as_slice()),
82 None => (doc, &[][..]),
83 };
84
85 let ctx = FactoryCtx {
86 params: &doc.params,
87 sources: &doc.sources,
88 };
89
90 let mut gb = GraphBuilder::new();
91 let mut pending: Vec<(String, Vec<crate::registry::Connection>)> = Vec::new();
92
93 for (id, spec) in &doc.nodes {
94 let factory = registry
95 .get(&spec.op)
96 .ok_or_else(|| BuildGraphError::UnknownOp {
97 node: id.clone(),
98 op: spec.op.clone(),
99 })?;
100
101 let built = factory
102 .build(&spec.fields, &ctx)
103 .map_err(|e| BuildGraphError::Factory {
104 node: id.clone(),
105 source: e,
106 })?;
107
108 gb.add_node(id.clone(), built.node);
109 pending.push((id.clone(), built.connections));
110 }
111
112 for (dst, conns) in pending {
113 for c in conns {
114 gb.connect(c.src, dst.clone(), c.port);
115 }
116 }
117
118 gb.set_output(doc.output.as_str().to_string());
119 let graph = gb.build()?;
120
121 for check in kind_checks {
125 let Some(ix) = graph.index_of(&check.node) else {
126 continue;
129 };
130 let got = graph.output_kind(ix);
131 let expected = port_kind(check.declared);
132 if got != expected {
133 return Err(match &check.input {
134 Some(input) => BuildGraphError::FuncInputKind {
135 call: check.call.clone(),
136 func: check.func.clone(),
137 input: input.clone(),
138 expected,
139 src: check.node.clone(),
140 got,
141 },
142 None => BuildGraphError::FuncOutputKind {
143 call: check.call.clone(),
144 func: check.func.clone(),
145 declared: expected,
146 got,
147 },
148 });
149 }
150 }
151
152 if let Some(legend) = &doc.legend {
158 for entry in &legend.entries {
159 let src = entry.from.as_str();
160 let Some(ix) = graph.index_of(src) else {
161 return Err(BuildGraphError::LegendUnknownNode {
162 label: entry.label.clone(),
163 src: src.to_string(),
164 });
165 };
166 let got = graph.output_kind(ix);
167 if got != PortKind::Raster {
168 return Err(BuildGraphError::LegendNodeKind {
169 label: entry.label.clone(),
170 src: src.to_string(),
171 expected: PortKind::Raster,
172 got,
173 });
174 }
175 }
176 }
177 Ok(graph)
178}