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 UnknownInstance(String, Vec<String>),
138 AmbiguousNode(String, Vec<String>),
139 RefAboveRoot(String, String),
140 UnknownBuiltin(String),
141 BadArity(String),
142 Refused(Box<Diagnostic>),
144 UnresolvedBars(Located),
146 Abandoned,
148}
149
150impl EngineError {
151 pub fn code(&self) -> &str {
152 match self {
153 EngineError::Binding { fault, .. } => fault.code(),
154 EngineError::UnknownNode(_) | EngineError::UnknownInstance(..) => "engine.unknown_node",
155 EngineError::AmbiguousNode(..) => "engine.ambiguous_node",
156 EngineError::RefAboveRoot(..) => "engine.ref_above_root",
157 EngineError::UnknownBuiltin(_) => "engine.unknown_builtin",
158 EngineError::BadArity(_) => "engine.bad_arity",
159 EngineError::Refused(d) => &d.code,
160 EngineError::UnresolvedBars(_) => "engine.unresolved_bar_literal",
161 EngineError::Abandoned => "render.abandoned",
162 }
163 }
164
165 pub fn at(&self) -> Option<&Located> {
167 match self {
168 EngineError::Refused(d) => Some(&d.location),
169 EngineError::UnresolvedBars(at) => Some(at),
170 _ => None,
171 }
172 }
173
174 pub fn refused(d: Diagnostic) -> EngineError {
175 EngineError::Refused(Box::new(d))
176 }
177
178 pub fn of_closed_form(r: &Refusal, at: Located, help: impl Into<String>) -> EngineError {
181 EngineError::refused(Diagnostic {
182 code: r.code.as_str().to_string(),
183 message: r.message.clone(),
184 location: at,
185 help: help.into(),
186 })
187 }
188}
189
190impl fmt::Display for EngineError {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 match self {
193 EngineError::Binding { node, fault, .. } => write!(f, "`{node}`: {fault}"),
194 EngineError::UnknownNode(p) => write!(f, "no such node: `{p}`"),
195 EngineError::UnknownInstance(p, names) => write!(
196 f,
197 "no such node: `{p}`; this render's instances of its file are named {}, a \
198 reading names one exactly as listed",
199 names
200 .iter()
201 .map(|n| format!("`@{n}`"))
202 .collect::<Vec<_>>()
203 .join(", ")
204 ),
205 EngineError::AmbiguousNode(file, names) => write!(
206 f,
207 "`{file}` is a file with {} instances; name one: {}",
208 names.len(),
209 names.join(", ")
210 ),
211 EngineError::RefAboveRoot(node, path) => {
212 write!(f, "`@{path}` in `{node}` walks above the composition root")
213 }
214 EngineError::UnknownBuiltin(n) => write!(
215 f,
216 "unresolved function `{n}` (no builtin or sugar implementation)"
217 ),
218 EngineError::BadArity(n) => write!(f, "wrong argument shape for `{n}`"),
219 EngineError::Refused(d) => write!(f, "{d}"),
220 EngineError::UnresolvedBars(at) => write!(
221 f,
222 "`{at}` holds a bar literal and this composition declares no bpm/meter"
223 ),
224 EngineError::Abandoned => write!(f, "the render was let go before it ended"),
225 }
226 }
227}
228
229impl std::error::Error for EngineError {}
230
231pub static REGISTRY: [(&str, &str); 101] = [
234 (
235 "type.no_overload",
236 "a builtin applied to operand types no row names",
237 ),
238 ("type.domain_mismatch", "a closed form in t meets one in f"),
239 (
240 "type.samples_in_closed_form",
241 "a closed form meets samples or frames",
242 ),
243 (
244 "type.samples_in_series",
245 "a sum to inf whose term is samples, which only a finite sum writes out",
246 ),
247 (
248 "engine.series_too_long",
249 "a finite sum written out a term at a time past the terms one lowering holds",
250 ),
251 (
252 "type.rate_conflict",
253 "two sampled operands at different rates",
254 ),
255 (
256 "type.frame_mismatch",
257 "two framed operands at different windows or hops",
258 ),
259 ("type.width_mismatch", "widths differ and neither is one"),
260 (
261 "type.filter_needs_a_dual",
262 "a filter needs a closed form with a dual, and this one has none",
263 ),
264 (
265 "type.non_affine_singular",
266 "delta or pv with a non-affine argument",
267 ),
268 (
269 "type.series_not_summable",
270 "a sum to inf whose dual does not converge",
271 ),
272 (
273 "type.self_gain_unbounded",
274 "a linear self whose loop gain does not settle",
275 ),
276 (
277 "type.discrete_self_at_time",
278 "a discrete loop reading its own past at an instant rather than by index",
279 ),
280 (
281 "type.join_over_self",
282 "a join operand reading its own loop whole, so no width holds the node",
283 ),
284 (
285 "type.stateful_warp",
286 "a node that holds state read at a time that moves",
287 ),
288 (
289 "type.bars_in_frequency",
290 "a bar unit inside a closed form in f",
291 ),
292 (
293 "type.non_integer_power",
294 "a real exponent over a base that is not a positive constant",
295 ),
296 (
297 "type.non_integer_index",
298 "an index that is no integer: a time, a fraction, or a ref",
299 ),
300 (
301 "type.index_outside_read",
302 "idx(...) anywhere but inside an index read",
303 ),
304 ("cast.left_algebra", "fourier or ifourier on a non-pair"),
305 (
306 "cast.samples_are_terminal",
307 "fourier or ifourier on samples or frames",
308 ),
309 ("cast.stft_needs_samples", "stft on anything but samples"),
310 ("cast.istft_needs_frames", "istft on anything but frames"),
311 ("cast.missing_window", "stft without window= or hop="),
312 (
313 "collapse.not_finite",
314 "a closed form past the largest double where it is read",
315 ),
316 (
317 "cast.window_off_the_lattice",
318 "an stft window or hop that is no whole number of steps of the rate in use",
319 ),
320 (
321 "collapse.empty_band",
322 "every line sits at or above the ceiling",
323 ),
324 (
325 "collapse.singular_in_ct",
326 "a delta or pv atom reaches point sampling",
327 ),
328 (
329 "collapse.not_evaluable",
330 "a subterm with no value at a point",
331 ),
332 (
333 "collapse.series_nesting",
334 "series nested past what one instant expands",
335 ),
336 (
337 "collapse.no_block_row",
338 "a closed form in f read block by block, which one transform over an extent places",
339 ),
340 (
341 "samples.zero_delay_loop",
342 "a sampled loop reads no written sample",
343 ),
344 (
345 "samples.hop_outside_cola",
346 "an stft hop the window does not overlap-add across",
347 ),
348 (
349 "samples.window_not_power_of_two",
350 "an stft window that is not a power of two",
351 ),
352 (
353 "samples.channel_out_of_range",
354 "a component past the width of the value it was asked of",
355 ),
356 (
357 "samples.own_width",
358 "a node's program writing another width than its samples hold, an engine defect",
359 ),
360 (
361 "samples.grid_too_large",
362 "a finite-difference grid past what one call may hold at this rate",
363 ),
364 (
365 "samples.string_past_rate",
366 "a string whose first two partials no stable grid rings at this rate",
367 ),
368 (
369 "samples.bridge_unstable",
370 "a unison whose bridge leaves its scheme not provably stable at this rate",
371 ),
372 (
373 "samples.contact_unsettled",
374 "a contact force whose solve reached no value at some sample",
375 ),
376 (
377 "samples.argument_out_of_range",
378 "a solver's varying parameter outside its range at some sample",
379 ),
380 (
381 "engine.moving_energy_parameter",
382 "a stored-energy coefficient that moves between its jumps",
383 ),
384 (
385 "grammar.unknown_name",
386 "an identifier that is no builtin, unit or note name",
387 ),
388 ("grammar.arity", "a call with the wrong argument count"),
389 (
390 "grammar.unknown_named_argument",
391 "a named argument the builtin does not read",
392 ),
393 (
394 "engine.infinite_value",
395 "inf where it names no number: inf - inf, 0*inf, or in a term that moves",
396 ),
397 (
398 "engine.unknown_node",
399 "a target no node of this composition answers for",
400 ),
401 (
402 "engine.ambiguous_node",
403 "a path more than one node answers for",
404 ),
405 (
406 "engine.ref_above_root",
407 "a ref reaching outside the composition",
408 ),
409 ("engine.unknown_builtin", "a call no builtin table names"),
410 (
411 "engine.physics_out_of_range",
412 "a finite-difference argument outside the range its model admits",
413 ),
414 (
415 "engine.bad_arity",
416 "a builtin whose own lowering wanted other arguments",
417 ),
418 (
419 "engine.unresolved_bar_literal",
420 "a bar literal with no bpm and meter behind it",
421 ),
422 (
423 "engine.unbound_variable",
424 "a body reading a name no invocation bound",
425 ),
426 ("engine.unused_argument", "an argument the body never reads"),
427 (
428 "engine.reserved_parameter",
429 "a parameter named t, f, pi, i or self",
430 ),
431 ("engine.duplicate_argument", "one argument given twice"),
432 ("engine.self_in_argument", "self written inside an argument"),
433 (
434 "engine.shifted_parameter_read",
435 "a parameter read at a shifted time",
436 ),
437 (
438 "engine.too_many_instances",
439 "more argument tuples than one node may hold",
440 ),
441 (
442 "engine.default_reads_buffer",
443 "a default naming what settles to no one number",
444 ),
445 (
446 "engine.cyclic_substitution",
447 "a closed form reaching itself around a loop of refs",
448 ),
449 (
450 "read.no_spectral_sum",
451 "an exact reading of a term no atom sum reaches",
452 ),
453 (
454 "read.series_not_enumerable",
455 "a line reading of a series whose term is no line",
456 ),
457 (
458 "read.lines_need_unwindowed_lines",
459 "a line reading of a term a window gave a width",
460 ),
461 (
462 "read.no_partial_at_instant",
463 "a harmonic reading of a term with no partial at an instant: an impulse, or a pole there",
464 ),
465 (
466 "read.too_many_partial_pairs",
467 "a harmonic reading of a frame whose partials over the floor pair past the cap",
468 ),
469 (
470 "read.window_under_two_periods",
471 "a sliding-window reading's window under twice the shortest period it reads",
472 ),
473 (
474 "read.target_under_window",
475 "a sliding-window reading of a target shorter than its window",
476 ),
477 (
478 "read.rate_under_bands",
479 "a band-envelope reading of samples at a rate that places no ERB band",
480 ),
481 (
482 "read.too_many_cells",
483 "a self-similarity matrix whose cells over the target pass the cap",
484 ),
485 (
486 "read.kernel_past_cap",
487 "a self-similarity novelty kernel wider a side than the cells a matrix holds",
488 ),
489 (
490 "read.cell_under_band_step",
491 "a self-similarity cell holding no sample of the band envelopes",
492 ),
493 (
494 "read.target_under_frame",
495 "an alias score of a target shorter than the one frame it reads",
496 ),
497 (
498 "read.frame_under_order",
499 "a formant frame holding fewer samples than its all-pole fit's lags",
500 ),
501 (
502 "read.target_under_order",
503 "a formant reading of a target holding fewer samples than its all-pole fit's lags",
504 ),
505 (
506 "read.rate_under_weighting",
507 "a loudness of samples at a rate whose Nyquist the K-weighting shelf stands at or past",
508 ),
509 (
510 "engine.bad_crop_shoulder",
511 "a crop shoulder outside the window it belongs to",
512 ),
513 (
514 "engine.filter_without_response",
515 "a filter whose q or gain leaves its response no finite pole, zero or gain",
516 ),
517 (
518 "engine.non_constant_argument",
519 "a number-valued argument that moves",
520 ),
521 (
522 "engine.reads_ahead",
523 "a stream reading a sample its source has not computed yet",
524 ),
525 (
526 "engine.unreadable_position",
527 "an exactly computed time past what its integers hold",
528 ),
529 (
530 "engine.per_lane_read_on_samples",
531 "a buffer read at one time per component",
532 ),
533 (
534 "engine.forward_self_read",
535 "a self-reference reaching forward in time",
536 ),
537 (
538 "engine.series_body_not_inlinable",
539 "a closed loop whose body holds a value no term carries",
540 ),
541 (
542 "engine.unshadered_operation",
543 "an operation the shader builder has no arm for",
544 ),
545 (
546 "engine.not_materialized",
547 "a reading off samples this render never held",
548 ),
549 (
550 "engine.observation_needs_samples",
551 "a sample-consuming reading asked of a closed form",
552 ),
553 (
554 "engine.observation_needs_a_graph",
555 "a reading asked of a buffer with no node behind it",
556 ),
557 (
558 "engine.observation_not_wired",
559 "a reading no buffer answers",
560 ),
561 (
562 "engine.alias_needs_a_closed_form",
563 "an alias score asked of samples with no closed form behind them",
564 ),
565 (
566 "engine.no_stream",
567 "a stream over a node no block reads alone, or a target it cannot open",
568 ),
569 (
570 "engine.stream_behind",
571 "a stream read from a sample before where it stands",
572 ),
573 ("engine.empty_read", "a stream read of no samples"),
574 (
575 "engine.stream_width",
576 "a stream, or an edit of one, wider than the channels it plays",
577 ),
578 (
579 "engine.unbounded_extent",
580 "a node read whole whose input never ends",
581 ),
582 (
583 "engine.unwritten_series",
584 "a sum written out one node per index whose bounds name no whole number in this instance",
585 ),
586 (
587 "engine.unbounded_read",
588 "a value asked for every sample of a support that never starts or ends",
589 ),
590 (
591 "render.no_end",
592 "an interval with no end over a root whose support never ends",
593 ),
594 (
595 "render.unrepresentable_sample",
596 "a sample past what the output's float or integer PCM encoding holds",
597 ),
598 (
599 "render.abandoned",
600 "a render its caller let go before it ended",
601 ),
602];