1use std::fmt;
4
5use sva_ast::ByteSpan;
6use sva_formula::Refusal;
7
8#[derive(Clone, Debug, PartialEq)]
10pub enum BindingFault {
11 Unbound(String, String),
12 Unused(String, String),
13 UnusedPositional(String, String),
15 Reserved(String),
16 Duplicate(String),
17 SelfInArgument(String),
18 ShiftedRead(String, String),
19 TooManyInstances(usize),
20 DefaultNamesNoNumber(String, String),
21}
22
23impl BindingFault {
24 pub fn code(&self) -> &'static str {
25 match self {
26 BindingFault::Unbound(..) => "engine.unbound_variable",
27 BindingFault::Unused(..) | BindingFault::UnusedPositional(..) => {
28 "engine.unused_argument"
29 }
30 BindingFault::Reserved(_) => "engine.reserved_parameter",
31 BindingFault::Duplicate(_) => "engine.duplicate_argument",
32 BindingFault::SelfInArgument(_) => "engine.self_in_argument",
33 BindingFault::ShiftedRead(..) => "engine.shifted_parameter_read",
34 BindingFault::TooManyInstances(_) => "engine.too_many_instances",
35 BindingFault::DefaultNamesNoNumber(..) => "engine.default_reads_buffer",
37 }
38 }
39}
40
41impl fmt::Display for BindingFault {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 BindingFault::Unbound(callee, v) => write!(
45 f,
46 "`{v}` is free in `{callee}` and this invocation binds no `{v}=`"
47 ),
48 BindingFault::Unused(callee, v) => {
49 write!(f, "`{v}=` binds nothing — `{callee}` leaves no `{v}` free")
50 }
51 BindingFault::UnusedPositional(callee, arg) => write!(
52 f,
53 "the positional argument `{arg}` binds nothing — `{callee}` reads no signal \
54 passed to it; name each argument, or read it at a time as `@{callee}({arg}, ...)`"
55 ),
56 BindingFault::Reserved(v) => {
57 write!(
58 f,
59 "`{v}` is already bound by the language and cannot be a parameter"
60 )
61 }
62 BindingFault::Duplicate(v) => {
63 write!(f, "`{v}=` is given twice in one invocation")
64 }
65 BindingFault::SelfInArgument(v) => write!(
66 f,
67 "`{v}=` passes a `self(...)`, which would read the callee's own output, not \
68 this node's; give it a file to reference instead"
69 ),
70 BindingFault::ShiftedRead(v, what) => write!(
71 f,
72 "`{v}` is read at a shifted time but its argument holds `{what}`, whose value \
73 follows a time of its own rather than the `t` handed to it; pass a file"
74 ),
75 BindingFault::TooManyInstances(n) => {
76 write!(f, "this composition expands past {n} function invocations")
77 }
78 BindingFault::DefaultNamesNoNumber(file, what) => write!(
79 f,
80 "`{file}`'s default names `{what}`, which settles to no one number; a default \
81 resolves with no invocation behind it, so a signal is what a caller passes"
82 ),
83 }
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq, Default)]
90pub struct Located {
91 pub node: String,
92 pub span: Option<ByteSpan>,
93}
94
95impl Located {
96 pub fn at(node: impl Into<String>, span: Option<ByteSpan>) -> Located {
97 Located {
98 node: node.into(),
99 span,
100 }
101 }
102}
103
104impl fmt::Display for Located {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self.span {
107 Some(span) => write!(f, "{}:{}", self.node, span.start),
108 None => write!(f, "{}", self.node),
109 }
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct Diagnostic {
116 pub code: String,
117 pub message: String,
118 pub location: Located,
119 pub help: String,
120}
121
122impl fmt::Display for Diagnostic {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 write!(f, "{} ({}). {}", self.message, self.location, self.help)
125 }
126}
127
128#[derive(Clone, Debug, PartialEq)]
129pub enum EngineError {
130 Binding {
131 node: String,
132 span: Option<ByteSpan>,
133 fault: BindingFault,
134 },
135 UnknownNode(String),
136 AmbiguousNode(String, Vec<String>),
137 RefAboveRoot(String, String),
138 UnknownBuiltin(String),
139 BadArity(String),
140 Refused(Box<Diagnostic>),
142 UnresolvedBars(Located),
144 Abandoned,
146}
147
148impl EngineError {
149 pub fn code(&self) -> &str {
150 match self {
151 EngineError::Binding { fault, .. } => fault.code(),
152 EngineError::UnknownNode(_) => "engine.unknown_node",
153 EngineError::AmbiguousNode(..) => "engine.ambiguous_node",
154 EngineError::RefAboveRoot(..) => "engine.ref_above_root",
155 EngineError::UnknownBuiltin(_) => "engine.unknown_builtin",
156 EngineError::BadArity(_) => "engine.bad_arity",
157 EngineError::Refused(d) => &d.code,
158 EngineError::UnresolvedBars(_) => "engine.unresolved_bar_literal",
159 EngineError::Abandoned => "render.abandoned",
160 }
161 }
162
163 pub fn at(&self) -> Option<&Located> {
165 match self {
166 EngineError::Refused(d) => Some(&d.location),
167 EngineError::UnresolvedBars(at) => Some(at),
168 _ => None,
169 }
170 }
171
172 pub fn refused(d: Diagnostic) -> EngineError {
173 EngineError::Refused(Box::new(d))
174 }
175
176 pub fn of_closed_form(r: &Refusal, at: Located, help: impl Into<String>) -> EngineError {
179 EngineError::refused(Diagnostic {
180 code: r.code.as_str().to_string(),
181 message: r.message.clone(),
182 location: at,
183 help: help.into(),
184 })
185 }
186}
187
188impl fmt::Display for EngineError {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 match self {
191 EngineError::Binding { node, fault, .. } => write!(f, "`{node}`: {fault}"),
192 EngineError::UnknownNode(p) => write!(f, "no such node: `{p}`"),
193 EngineError::AmbiguousNode(file, names) => write!(
194 f,
195 "`{file}` is a file with {} instances; name one: {}",
196 names.len(),
197 names.join(", ")
198 ),
199 EngineError::RefAboveRoot(node, path) => {
200 write!(f, "`@{path}` in `{node}` walks above the composition root")
201 }
202 EngineError::UnknownBuiltin(n) => write!(
203 f,
204 "unresolved function `{n}` (no builtin or sugar implementation)"
205 ),
206 EngineError::BadArity(n) => write!(f, "wrong argument shape for `{n}`"),
207 EngineError::Refused(d) => write!(f, "{d}"),
208 EngineError::UnresolvedBars(at) => write!(
209 f,
210 "`{at}` holds a bar literal and this composition declares no bpm/meter"
211 ),
212 EngineError::Abandoned => write!(f, "the render was let go before it ended"),
213 }
214 }
215}
216
217impl std::error::Error for EngineError {}
218
219pub static REGISTRY: [(&str, &str); 94] = [
222 (
223 "type.no_overload",
224 "a builtin applied to operand types no row names",
225 ),
226 ("type.domain_mismatch", "a closed form in t meets one in f"),
227 (
228 "type.samples_in_closed_form",
229 "a closed form meets samples or frames",
230 ),
231 (
232 "type.samples_in_series",
233 "a sum to inf whose term is samples, which only a finite sum writes out",
234 ),
235 (
236 "engine.series_too_long",
237 "a finite sum written out a term at a time past the terms one lowering holds",
238 ),
239 (
240 "type.rate_conflict",
241 "two sampled operands at different rates",
242 ),
243 (
244 "type.frame_mismatch",
245 "two framed operands at different windows or hops",
246 ),
247 ("type.width_mismatch", "widths differ and neither is one"),
248 (
249 "type.filter_needs_a_dual",
250 "a filter needs a closed form with a dual, and this one has none",
251 ),
252 (
253 "type.non_affine_singular",
254 "delta or pv with a non-affine argument",
255 ),
256 (
257 "type.series_not_summable",
258 "a sum to inf whose dual does not converge",
259 ),
260 (
261 "type.self_gain_unbounded",
262 "a linear self whose loop gain does not settle",
263 ),
264 (
265 "type.discrete_self_at_time",
266 "a discrete loop reading its own past at an instant rather than by index",
267 ),
268 (
269 "type.join_over_self",
270 "a join operand reading its own loop whole, so no width holds the node",
271 ),
272 (
273 "type.stateful_warp",
274 "a node that holds state read at a time that moves",
275 ),
276 (
277 "type.bars_in_frequency",
278 "a bar unit inside a closed form in f",
279 ),
280 (
281 "type.non_integer_power",
282 "a real exponent over a base that is not a positive constant",
283 ),
284 (
285 "type.non_integer_index",
286 "an index that is no integer: a time, a fraction, or a ref",
287 ),
288 (
289 "type.index_outside_read",
290 "idx(...) anywhere but inside an index read",
291 ),
292 ("cast.left_algebra", "fourier or ifourier on a non-pair"),
293 (
294 "cast.samples_are_terminal",
295 "fourier or ifourier on samples or frames",
296 ),
297 ("cast.stft_needs_samples", "stft on anything but samples"),
298 ("cast.istft_needs_frames", "istft on anything but frames"),
299 ("cast.missing_window", "stft without window= or hop="),
300 (
301 "collapse.not_finite",
302 "a closed form past the largest double where it is read",
303 ),
304 (
305 "cast.window_off_the_lattice",
306 "an stft window or hop that is no whole number of steps of the rate in use",
307 ),
308 (
309 "collapse.empty_band",
310 "every line sits at or above the ceiling",
311 ),
312 (
313 "collapse.singular_in_ct",
314 "a delta or pv atom reaches point sampling",
315 ),
316 (
317 "collapse.not_evaluable",
318 "a subterm with no value at a point",
319 ),
320 (
321 "collapse.series_nesting",
322 "series nested past what one instant expands",
323 ),
324 (
325 "collapse.no_block_row",
326 "a closed form in f read block by block, which one transform over an extent places",
327 ),
328 (
329 "samples.zero_delay_loop",
330 "a sampled loop reads no written sample",
331 ),
332 (
333 "samples.hop_outside_cola",
334 "an stft hop the window does not overlap-add across",
335 ),
336 (
337 "samples.window_not_power_of_two",
338 "an stft window that is not a power of two",
339 ),
340 (
341 "samples.channel_out_of_range",
342 "a component past the width of the value it was asked of",
343 ),
344 (
345 "samples.own_width",
346 "a node's program writing another width than its samples hold, an engine defect",
347 ),
348 (
349 "samples.grid_too_large",
350 "a finite-difference grid past what one call may hold at this rate",
351 ),
352 (
353 "samples.string_past_rate",
354 "a string whose first two partials no stable grid rings at this rate",
355 ),
356 (
357 "samples.bridge_unstable",
358 "a unison whose bridge leaves its scheme not provably stable at this rate",
359 ),
360 (
361 "samples.contact_unsettled",
362 "a contact force whose solve reached no value at some sample",
363 ),
364 (
365 "samples.argument_out_of_range",
366 "a solver's varying parameter outside its range at some sample",
367 ),
368 (
369 "engine.moving_energy_parameter",
370 "a stored-energy coefficient that moves between its jumps",
371 ),
372 (
373 "grammar.unknown_name",
374 "an identifier that is no builtin, unit or note name",
375 ),
376 ("grammar.arity", "a call with the wrong argument count"),
377 (
378 "grammar.unknown_named_argument",
379 "a named argument the builtin does not read",
380 ),
381 (
382 "engine.infinite_value",
383 "inf where it names no number: inf - inf, 0*inf, or in a term that moves",
384 ),
385 (
386 "engine.unknown_node",
387 "a target no node of this composition answers for",
388 ),
389 (
390 "engine.ambiguous_node",
391 "a path more than one node answers for",
392 ),
393 (
394 "engine.ref_above_root",
395 "a ref reaching outside the composition",
396 ),
397 ("engine.unknown_builtin", "a call no builtin table names"),
398 (
399 "engine.physics_out_of_range",
400 "a finite-difference argument outside the range its model admits",
401 ),
402 (
403 "engine.bad_arity",
404 "a builtin whose own lowering wanted other arguments",
405 ),
406 (
407 "engine.unresolved_bar_literal",
408 "a bar literal with no bpm and meter behind it",
409 ),
410 (
411 "engine.unbound_variable",
412 "a body reading a name no invocation bound",
413 ),
414 ("engine.unused_argument", "an argument the body never reads"),
415 (
416 "engine.reserved_parameter",
417 "a parameter named t, f, pi, i or self",
418 ),
419 ("engine.duplicate_argument", "one argument given twice"),
420 ("engine.self_in_argument", "self written inside an argument"),
421 (
422 "engine.shifted_parameter_read",
423 "a parameter read at a shifted time",
424 ),
425 (
426 "engine.too_many_instances",
427 "more argument tuples than one node may hold",
428 ),
429 (
430 "engine.default_reads_buffer",
431 "a default naming what settles to no one number",
432 ),
433 (
434 "engine.cyclic_substitution",
435 "a closed form reaching itself around a loop of refs",
436 ),
437 (
438 "read.no_spectral_sum",
439 "an exact reading of a term no atom sum reaches",
440 ),
441 (
442 "read.series_not_enumerable",
443 "a line reading of a series whose term is no line",
444 ),
445 (
446 "read.lines_need_unwindowed_lines",
447 "a line reading of a term a window gave a width",
448 ),
449 (
450 "read.no_partial_at_instant",
451 "a harmonic reading of a term with no partial at an instant: an impulse, or a pole there",
452 ),
453 (
454 "read.too_many_partial_pairs",
455 "a harmonic reading of a frame whose partials over the floor pair past the cap",
456 ),
457 (
458 "read.window_under_two_periods",
459 "a sliding-window reading's window under twice the longest period it reads",
460 ),
461 (
462 "read.target_under_window",
463 "a sliding-window reading of a target shorter than its window",
464 ),
465 (
466 "read.too_many_cells",
467 "a self-similarity matrix whose cells over the target pass the cap",
468 ),
469 (
470 "engine.bad_crop_shoulder",
471 "a crop shoulder outside the window it belongs to",
472 ),
473 (
474 "engine.filter_without_response",
475 "a filter whose q or gain leaves its response no finite pole, zero or gain",
476 ),
477 (
478 "engine.non_constant_argument",
479 "a number-valued argument that moves",
480 ),
481 (
482 "engine.reads_ahead",
483 "a stream reading a sample its source has not computed yet",
484 ),
485 (
486 "engine.unreadable_position",
487 "an exactly computed time past what its integers hold",
488 ),
489 (
490 "engine.per_lane_read_on_samples",
491 "a buffer read at one time per component",
492 ),
493 (
494 "engine.forward_self_read",
495 "a self-reference reaching forward in time",
496 ),
497 (
498 "engine.series_body_not_inlinable",
499 "a closed loop whose body holds a value no term carries",
500 ),
501 (
502 "engine.unshadered_operation",
503 "an operation the shader builder has no arm for",
504 ),
505 (
506 "engine.not_materialized",
507 "a reading off samples this render never held",
508 ),
509 (
510 "engine.observation_needs_samples",
511 "a sample-consuming reading asked of a closed form",
512 ),
513 (
514 "engine.observation_needs_a_graph",
515 "a reading asked of a buffer with no node behind it",
516 ),
517 (
518 "engine.observation_not_wired",
519 "a reading no buffer answers",
520 ),
521 (
522 "engine.alias_needs_a_closed_form",
523 "an alias score asked of samples with no closed form behind them",
524 ),
525 (
526 "engine.no_stream",
527 "a stream over a node no block reads alone, or a target it cannot open",
528 ),
529 (
530 "engine.stream_behind",
531 "a stream read from a sample before where it stands",
532 ),
533 ("engine.empty_read", "a stream read of no samples"),
534 (
535 "engine.stream_width",
536 "a stream, or an edit of one, wider than the channels it plays",
537 ),
538 (
539 "engine.unbounded_extent",
540 "a node read whole whose input never ends",
541 ),
542 (
543 "engine.unwritten_series",
544 "a sum written out one node per index whose bounds name no whole number in this instance",
545 ),
546 (
547 "engine.unbounded_read",
548 "a value asked for every sample of a support that never starts or ends",
549 ),
550 (
551 "render.no_end",
552 "an interval with no end over a root whose support never ends",
553 ),
554 (
555 "render.unrepresentable_sample",
556 "a sample past what the output's float or integer PCM encoding holds",
557 ),
558 (
559 "render.abandoned",
560 "a render its caller let go before it ended",
561 ),
562];