1use std::collections::BTreeMap;
31
32use engawa::{
33 BindingKind, Material, Node, NodeId, RenderGraph, ResourceId, ResourceKind,
34 ShaderSource, UniformBinding,
35};
36use thiserror::Error;
37
38use crate::parse::Span;
39use crate::sexpr::{Sexpr, SexprKind};
40
41#[derive(Debug, Error, Clone, PartialEq)]
42pub enum LowerError {
43 #[error("expected form, got non-list at line {line}, column {col}")]
44 NotAForm { line: usize, col: usize },
45 #[error("expected form name (symbol) at line {line}, column {col}")]
46 MissingFormName { line: usize, col: usize },
47 #[error("unknown form {form:?} at line {line}, column {col}")]
48 UnknownForm {
49 form: String,
50 line: usize,
51 col: usize,
52 },
53 #[error("form {form:?} expects {expected} arguments, got {got} at line {line}")]
54 Arity {
55 form: String,
56 expected: usize,
57 got: usize,
58 line: usize,
59 },
60 #[error("form {form:?} expects argument {position} to be a {kind} at line {line}")]
61 BadArgKind {
62 form: String,
63 position: usize,
64 kind: &'static str,
65 line: usize,
66 },
67 #[error("could not parse {value:?} as {kind} at line {line}")]
68 BadNumber {
69 value: String,
70 kind: &'static str,
71 line: usize,
72 },
73 #[error("unknown resource kind {found:?} at line {line}")]
74 UnknownResourceKind { found: String, line: usize },
75 #[error("unknown binding kind {found:?} at line {line}")]
76 UnknownBindingKind { found: String, line: usize },
77 #[error("unknown node kind {found:?} at line {line}")]
78 UnknownNodeKind { found: String, line: usize },
79 #[error("material {name:?} referenced but not defined")]
80 UndefinedMaterial { name: String },
81}
82
83pub fn lower_to_graph(forms: &[Sexpr]) -> Result<RenderGraph, LowerError> {
92 let mut materials: BTreeMap<String, Material> = BTreeMap::new();
93 let mut resources: BTreeMap<ResourceId, ResourceKind> = BTreeMap::new();
94 let mut graph_form: Option<&Sexpr> = None;
95
96 for form in forms {
97 let items = require_list(form)?;
98 let head_name = require_symbol(&items[0], "form-head", 0, form.span)?;
99 match head_name {
100 "defresource" => {
101 let (id, kind) = lower_resource(items, form.span)?;
102 resources.insert(id, kind);
103 }
104 "defmaterial" => {
105 let mat = lower_material(items, form.span)?;
106 materials.insert(mat.name.clone(), mat);
107 }
108 "defgraph" => {
109 if graph_form.is_some() {
110 return Err(LowerError::UnknownForm {
111 form: "defgraph (multiple)".to_string(),
112 line: form.span.line,
113 col: form.span.column,
114 });
115 }
116 graph_form = Some(form);
117 }
118 other => {
119 return Err(LowerError::UnknownForm {
120 form: other.to_string(),
121 line: form.span.line,
122 col: form.span.column,
123 });
124 }
125 }
126 }
127
128 let Some(graph_form) = graph_form else {
129 let mut g = RenderGraph::default();
132 for (id, kind) in resources {
133 g = g.with_resource(id, kind);
134 }
135 return Ok(g);
136 };
137
138 let items = require_list(graph_form)?;
139 let mut g = RenderGraph::default();
140 for (id, kind) in &resources {
141 g = g.with_resource(id.clone(), kind.clone());
142 }
143
144 let body = &items[1..];
146 let _graph_name = require_symbol(&items[1], "defgraph", 1, graph_form.span)?;
147 for clause in &body[1..] {
148 let clause_items = require_list(clause)?;
149 let head = require_symbol(&clause_items[0], "graph-clause", 0, clause.span)?;
150 match head {
151 "input" => {
152 let id = lower_resource_ref(clause_items, "input", clause.span)?;
153 g = g.with_input(id);
154 }
155 "output" => {
156 let id = lower_resource_ref(clause_items, "output", clause.span)?;
157 g = g.with_output(id);
158 }
159 "node" => {
160 let node = lower_node(clause_items, clause.span, &materials)?;
161 g = g.with_node(node);
162 }
163 other => {
164 return Err(LowerError::UnknownForm {
165 form: other.to_string(),
166 line: clause.span.line,
167 col: clause.span.column,
168 });
169 }
170 }
171 }
172 Ok(g)
173}
174
175fn lower_resource(
176 items: &[Sexpr],
177 span: Span,
178) -> Result<(ResourceId, ResourceKind), LowerError> {
179 if items.len() < 3 {
180 return Err(LowerError::Arity {
181 form: "defresource".into(),
182 expected: 3,
183 got: items.len(),
184 line: span.line,
185 });
186 }
187 let id_str = require_symbol(&items[1], "defresource", 1, span)?;
188 let id = ResourceId::new(id_str);
189 let kind_form = &items[2];
190 let kind = match &kind_form.kind {
191 SexprKind::Symbol(s) if s == "external" => ResourceKind::External,
192 SexprKind::Symbol(s) if s == "sampler" => ResourceKind::Sampler,
193 SexprKind::List(inner) => {
194 let head = require_symbol(&inner[0], "resource-kind", 0, kind_form.span)?;
195 match head {
196 "texture" => {
197 let w = parse_u32(&inner[1], "texture-width", kind_form.span)?;
198 let h = parse_u32(&inner[2], "texture-height", kind_form.span)?;
199 ResourceKind::Texture {
200 width: Some(w),
201 height: Some(h),
202 }
203 }
204 "uniform" => {
205 let n = parse_u32(&inner[1], "uniform-size", kind_form.span)?;
206 ResourceKind::Uniform { size_bytes: n }
207 }
208 "storage" => {
209 let n = parse_u32(&inner[1], "storage-size", kind_form.span)?;
210 ResourceKind::Storage { size_bytes: n }
211 }
212 other => {
213 return Err(LowerError::UnknownResourceKind {
214 found: other.to_string(),
215 line: kind_form.span.line,
216 });
217 }
218 }
219 }
220 _ => {
221 return Err(LowerError::UnknownResourceKind {
222 found: format!("{:?}", kind_form.kind),
223 line: kind_form.span.line,
224 });
225 }
226 };
227 Ok((id, kind))
228}
229
230fn lower_material(items: &[Sexpr], span: Span) -> Result<Material, LowerError> {
231 if items.len() < 3 {
232 return Err(LowerError::Arity {
233 form: "defmaterial".into(),
234 expected: 3,
235 got: items.len(),
236 line: span.line,
237 });
238 }
239 let name = require_symbol(&items[1], "defmaterial", 1, span)?.to_string();
240 let mut shader: Option<ShaderSource> = None;
241 let mut bindings: Vec<UniformBinding> = Vec::new();
242 for clause in &items[2..] {
243 let inner = require_list(clause)?;
244 let head = require_symbol(&inner[0], "material-clause", 0, clause.span)?;
245 match head {
246 "shader" => {
247 let body = require_list(&inner[1])?;
248 let kind_name = require_symbol(&body[0], "shader-kind", 0, clause.span)?;
249 let payload = require_string(&body[1], "shader-payload", 1, clause.span)?;
250 shader = Some(match kind_name {
251 "inline" => ShaderSource::inline(payload),
252 "path" => ShaderSource::path(payload),
253 other => {
254 return Err(LowerError::UnknownForm {
255 form: format!("shader-kind:{other}"),
256 line: clause.span.line,
257 col: clause.span.column,
258 });
259 }
260 });
261 }
262 "bindings" => {
263 for b in &inner[1..] {
264 bindings.push(lower_binding(b)?);
265 }
266 }
267 other => {
268 return Err(LowerError::UnknownForm {
269 form: other.to_string(),
270 line: clause.span.line,
271 col: clause.span.column,
272 });
273 }
274 }
275 }
276 Ok(Material {
277 name,
278 shader: shader.unwrap_or_else(|| ShaderSource::inline("")),
279 bindings,
280 })
281}
282
283fn lower_binding(form: &Sexpr) -> Result<UniformBinding, LowerError> {
284 let items = require_list(form)?;
285 let head = require_symbol(&items[0], "binding", 0, form.span)?;
286 if head != "binding" {
287 return Err(LowerError::UnknownForm {
288 form: head.to_string(),
289 line: form.span.line,
290 col: form.span.column,
291 });
292 }
293 let n = parse_u32(&items[1], "binding-index", form.span)?;
294 let kind_str = require_symbol(&items[2], "binding-kind", 2, form.span)?;
295 let kind = match kind_str {
296 "uniform" => BindingKind::Uniform,
297 "storage_read" | "storage-read" => BindingKind::StorageRead,
298 "storage_read_write" | "storage-read-write" => BindingKind::StorageReadWrite,
299 "texture" => BindingKind::Texture,
300 "sampler" => BindingKind::Sampler,
301 other => {
302 return Err(LowerError::UnknownBindingKind {
303 found: other.to_string(),
304 line: form.span.line,
305 });
306 }
307 };
308 let resource_str = require_string(&items[3], "binding-resource", 3, form.span)?;
309 Ok(UniformBinding {
310 binding: n,
311 kind,
312 resource: ResourceId::new(resource_str),
313 })
314}
315
316fn lower_resource_ref(
317 items: &[Sexpr],
318 form: &'static str,
319 span: Span,
320) -> Result<ResourceId, LowerError> {
321 if items.len() != 2 {
322 return Err(LowerError::Arity {
323 form: form.into(),
324 expected: 2,
325 got: items.len(),
326 line: span.line,
327 });
328 }
329 let s = require_symbol(&items[1], form, 1, span)?;
330 Ok(ResourceId::new(s))
331}
332
333fn lower_node(
334 items: &[Sexpr],
335 span: Span,
336 materials: &BTreeMap<String, Material>,
337) -> Result<Node, LowerError> {
338 if items.len() < 3 {
339 return Err(LowerError::Arity {
340 form: "node".into(),
341 expected: 3,
342 got: items.len(),
343 line: span.line,
344 });
345 }
346 let node_id_str = require_symbol(&items[1], "node", 1, span)?;
347 let node_id = NodeId::new(node_id_str);
348 let mut kind: Option<String> = None;
349 let mut material: Option<Material> = None;
350 let mut inputs: Vec<ResourceId> = Vec::new();
351 let mut outputs: Vec<ResourceId> = Vec::new();
352 for clause in &items[2..] {
353 let inner = require_list(clause)?;
354 let head = require_symbol(&inner[0], "node-clause", 0, clause.span)?;
355 match head {
356 "kind" => {
357 let s = require_symbol(&inner[1], "kind", 1, clause.span)?;
358 kind = Some(s.to_string());
359 }
360 "material" => {
361 let s = require_symbol(&inner[1], "material", 1, clause.span)?;
362 let m = materials.get(s).ok_or_else(|| LowerError::UndefinedMaterial {
363 name: s.to_string(),
364 })?;
365 material = Some(m.clone());
366 }
367 "input" => {
368 let s = require_symbol(&inner[1], "input", 1, clause.span)?;
369 inputs.push(ResourceId::new(s));
370 }
371 "output" => {
372 let s = require_symbol(&inner[1], "output", 1, clause.span)?;
373 outputs.push(ResourceId::new(s));
374 }
375 other => {
376 return Err(LowerError::UnknownForm {
377 form: other.to_string(),
378 line: clause.span.line,
379 col: clause.span.column,
380 });
381 }
382 }
383 }
384 let pass = engawa::PassKind::Render;
385 let _ = kind; Ok(Node {
389 id: node_id,
390 pass,
391 inputs,
392 outputs,
393 material,
394 })
395}
396
397fn require_list(s: &Sexpr) -> Result<&[Sexpr], LowerError> {
400 s.as_list().ok_or(LowerError::NotAForm {
401 line: s.span.line,
402 col: s.span.column,
403 })
404}
405
406fn require_symbol<'a>(
407 s: &'a Sexpr,
408 form: &'static str,
409 position: usize,
410 span: Span,
411) -> Result<&'a str, LowerError> {
412 s.as_symbol().ok_or(LowerError::BadArgKind {
413 form: form.into(),
414 position,
415 kind: "symbol",
416 line: span.line,
417 })
418}
419
420fn require_string<'a>(
421 s: &'a Sexpr,
422 form: &'static str,
423 position: usize,
424 span: Span,
425) -> Result<&'a str, LowerError> {
426 s.as_string().ok_or(LowerError::BadArgKind {
427 form: form.into(),
428 position,
429 kind: "string",
430 line: span.line,
431 })
432}
433
434fn parse_u32(s: &Sexpr, kind: &'static str, span: Span) -> Result<u32, LowerError> {
435 let txt = s.as_number().ok_or(LowerError::BadArgKind {
436 form: kind.into(),
437 position: 0,
438 kind: "number",
439 line: span.line,
440 })?;
441 txt.parse::<u32>().map_err(|_| LowerError::BadNumber {
442 value: txt.to_string(),
443 kind: "u32",
444 line: span.line,
445 })
446}