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); 106] = [
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.singular_in_ct",
322 "a delta or pv atom reaches point sampling",
323 ),
324 (
325 "collapse.not_evaluable",
326 "a subterm with no value at a point",
327 ),
328 (
329 "collapse.series_nesting",
330 "series nested past what one instant expands",
331 ),
332 (
333 "collapse.no_block_row",
334 "a closed form in f read block by block, which one transform over an extent places",
335 ),
336 (
337 "samples.zero_delay_loop",
338 "a sampled loop reads no written sample",
339 ),
340 (
341 "samples.hop_outside_cola",
342 "an stft hop the window does not overlap-add across",
343 ),
344 (
345 "samples.window_not_power_of_two",
346 "an stft window that is not a power of two",
347 ),
348 (
349 "samples.channel_out_of_range",
350 "a component past the width of the value it was asked of",
351 ),
352 (
353 "samples.own_width",
354 "a node's program writing another width than its samples hold, an engine defect",
355 ),
356 (
357 "samples.grid_too_large",
358 "a finite-difference grid past what one call may hold at this rate",
359 ),
360 (
361 "samples.string_past_rate",
362 "a string whose first two partials no stable grid rings at this rate",
363 ),
364 (
365 "samples.bridge_unstable",
366 "a unison whose bridge leaves its scheme not provably stable at this rate",
367 ),
368 (
369 "samples.contact_unsettled",
370 "a contact force whose solve reached no value at some sample",
371 ),
372 (
373 "samples.argument_out_of_range",
374 "a solver's varying parameter outside its range at some sample",
375 ),
376 (
377 "engine.moving_energy_parameter",
378 "a stored-energy coefficient that moves between its jumps",
379 ),
380 (
381 "grammar.unknown_name",
382 "an identifier that is no builtin, unit or note name",
383 ),
384 ("grammar.arity", "a call with the wrong argument count"),
385 (
386 "grammar.unknown_named_argument",
387 "a named argument the builtin does not read",
388 ),
389 (
390 "engine.infinite_value",
391 "inf where it names no number: inf - inf, 0*inf, or in a term that moves",
392 ),
393 (
394 "engine.unknown_node",
395 "a target no node of this composition answers for",
396 ),
397 (
398 "engine.ambiguous_node",
399 "a path more than one node answers for",
400 ),
401 (
402 "engine.ref_above_root",
403 "a ref reaching outside the composition",
404 ),
405 ("engine.unknown_builtin", "a call no builtin table names"),
406 (
407 "engine.physics_out_of_range",
408 "a finite-difference argument outside the range its model admits",
409 ),
410 (
411 "engine.bad_arity",
412 "a builtin whose own lowering wanted other arguments",
413 ),
414 (
415 "engine.bad_knots",
416 "a knot table naming no curve: an odd count, under two knots, a knot that moves or is \
417 infinite, a time before the one ahead of it, or no span",
418 ),
419 (
420 "engine.unresolved_bar_literal",
421 "a bar literal with no bpm and meter behind it",
422 ),
423 (
424 "engine.unbound_variable",
425 "a body reading a name no invocation bound",
426 ),
427 ("engine.unused_argument", "an argument the body never reads"),
428 (
429 "engine.reserved_parameter",
430 "a parameter named t, f, pi, i or self",
431 ),
432 ("engine.duplicate_argument", "one argument given twice"),
433 ("engine.self_in_argument", "self written inside an argument"),
434 (
435 "engine.shifted_parameter_read",
436 "a parameter read at a shifted time",
437 ),
438 (
439 "engine.too_many_instances",
440 "more argument tuples than one node may hold",
441 ),
442 (
443 "engine.default_reads_buffer",
444 "a default naming what settles to no one number",
445 ),
446 (
447 "engine.cyclic_substitution",
448 "a closed form reaching itself around a loop of refs",
449 ),
450 (
451 "read.no_spectral_sum",
452 "an exact reading of a term no atom sum reaches",
453 ),
454 (
455 "read.series_not_enumerable",
456 "a line reading of a series whose term is no line",
457 ),
458 (
459 "read.lines_need_unwindowed_lines",
460 "a line reading of a term a window gave a width",
461 ),
462 (
463 "read.no_partial_at_instant",
464 "a harmonic reading of a term with no partial at an instant: an impulse, or a pole there",
465 ),
466 (
467 "read.too_many_partial_pairs",
468 "a harmonic reading of a frame whose partials over the floor pair past the cap",
469 ),
470 (
471 "read.block_under_samples",
472 "a loudness window under the four samples a block a quarter of itself apart needs",
473 ),
474 (
475 "read.window_under_two_periods",
476 "a sliding-window reading's window under twice the shortest period it reads",
477 ),
478 (
479 "read.target_under_window",
480 "a sliding-window reading of a target shorter than its window",
481 ),
482 (
483 "read.rate_under_bands",
484 "a band-envelope reading of samples at a rate that places no ERB band",
485 ),
486 (
487 "read.too_many_cells",
488 "a self-similarity matrix whose cells over the target pass the cap",
489 ),
490 (
491 "read.kernel_past_cap",
492 "a self-similarity novelty kernel wider a side than the cells a matrix holds",
493 ),
494 (
495 "read.cell_under_band_step",
496 "a self-similarity cell holding no sample of the band envelopes",
497 ),
498 (
499 "read.target_under_frame",
500 "an alias score of a target shorter than the one frame it reads",
501 ),
502 (
503 "read.frame_under_order",
504 "a formant frame holding fewer samples than its all-pole fit's lags",
505 ),
506 (
507 "read.target_under_order",
508 "a formant reading of a target holding fewer samples than its all-pole fit's lags",
509 ),
510 (
511 "read.rate_under_weighting",
512 "a loudness of samples at a rate whose Nyquist the K-weighting shelf stands at or past",
513 ),
514 (
515 "engine.bad_crop_shoulder",
516 "a crop shoulder outside the window it belongs to",
517 ),
518 (
519 "engine.filter_without_response",
520 "a filter whose q or gain leaves its response no finite pole, zero or gain",
521 ),
522 (
523 "engine.level_not_a_ratio",
524 "a filter's gain or noise's color at or below zero, which no level in dB names",
525 ),
526 (
527 "engine.non_constant_argument",
528 "a number-valued argument that moves",
529 ),
530 (
531 "engine.reads_ahead",
532 "a stream reading a sample its source has not computed yet",
533 ),
534 (
535 "engine.unreadable_position",
536 "an exactly computed time past what its integers hold",
537 ),
538 (
539 "engine.per_lane_read_on_samples",
540 "a buffer read at one time per component",
541 ),
542 (
543 "engine.forward_self_read",
544 "a self-reference reaching forward in time",
545 ),
546 (
547 "engine.series_body_not_inlinable",
548 "a closed loop whose body holds a value no term carries",
549 ),
550 (
551 "engine.unshadered_operation",
552 "an operation the shader builder has no arm for",
553 ),
554 (
555 "engine.not_materialized",
556 "a reading off samples this render never held",
557 ),
558 (
559 "engine.observation_needs_samples",
560 "a sample-consuming reading asked of a closed form",
561 ),
562 (
563 "engine.observation_needs_a_graph",
564 "a reading asked of a buffer with no node behind it",
565 ),
566 (
567 "engine.observation_not_wired",
568 "a reading no buffer answers",
569 ),
570 (
571 "engine.alias_needs_a_closed_form",
572 "an alias score asked of samples with no closed form behind them",
573 ),
574 (
575 "engine.no_stream",
576 "a stream over a node no block reads alone, or a target it cannot open",
577 ),
578 (
579 "engine.stream_behind",
580 "a stream read from a sample before where it stands",
581 ),
582 ("engine.empty_read", "a stream read of no samples"),
583 (
584 "engine.stream_width",
585 "a stream, or an edit of one, wider than the channels it plays",
586 ),
587 (
588 "engine.unbounded_extent",
589 "a node read whole whose input never ends",
590 ),
591 (
592 "engine.unwritten_series",
593 "a sum written out one node per index whose bounds name no whole number in this instance",
594 ),
595 (
596 "engine.unbounded_read",
597 "a value asked for every sample of a support that never starts or ends",
598 ),
599 (
600 "render.no_end",
601 "an interval with no end over a root whose support never ends",
602 ),
603 (
604 "render.unrepresentable_sample",
605 "a sample past what the output's float or integer PCM encoding holds",
606 ),
607 (
608 "render.abandoned",
609 "a render its caller let go before it ended",
610 ),
611 (
612 "grammar.unresolved_path",
613 "a path no front end resolved to a recording",
614 ),
615 (
616 "grammar.recording_outside_sample",
617 "a recording read anywhere but as `sample`'s argument",
618 ),
619 (
620 "type.recording_width",
621 "a recording read in `f`, or of no channel or more than the widest value holds",
622 ),
623];