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 Reserved(String),
14 Duplicate(String),
15 SelfInArgument(String),
16 ShiftedRead(String, String),
17 TooManyInstances(usize),
18 DefaultNamesNoNumber(String, String),
19}
20
21impl BindingFault {
22 pub fn code(&self) -> &'static str {
23 match self {
24 BindingFault::Unbound(..) => "engine.unbound_variable",
25 BindingFault::Unused(..) => "engine.unused_argument",
26 BindingFault::Reserved(_) => "engine.reserved_parameter",
27 BindingFault::Duplicate(_) => "engine.duplicate_argument",
28 BindingFault::SelfInArgument(_) => "engine.self_in_argument",
29 BindingFault::ShiftedRead(..) => "engine.shifted_parameter_read",
30 BindingFault::TooManyInstances(_) => "engine.too_many_instances",
31 BindingFault::DefaultNamesNoNumber(..) => "engine.default_reads_buffer",
33 }
34 }
35}
36
37impl fmt::Display for BindingFault {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 BindingFault::Unbound(callee, v) => write!(
41 f,
42 "`{v}` is free in `{callee}` and this invocation binds no `{v}=`"
43 ),
44 BindingFault::Unused(callee, v) => {
45 write!(f, "`{v}=` binds nothing — `{callee}` leaves no `{v}` free")
46 }
47 BindingFault::Reserved(v) => {
48 write!(
49 f,
50 "`{v}` is already bound by the language and cannot be a parameter"
51 )
52 }
53 BindingFault::Duplicate(v) => {
54 write!(f, "`{v}=` is given twice in one invocation")
55 }
56 BindingFault::SelfInArgument(v) => write!(
57 f,
58 "`{v}=` passes a `self(...)`, which would read the callee's own output, not \
59 this node's; give it a file to reference instead"
60 ),
61 BindingFault::ShiftedRead(v, what) => write!(
62 f,
63 "`{v}` is read at a shifted time but its argument holds `{what}`, whose value \
64 depends on the sample being written rather than on that time; pass a file"
65 ),
66 BindingFault::TooManyInstances(n) => {
67 write!(f, "this composition expands past {n} function invocations")
68 }
69 BindingFault::DefaultNamesNoNumber(file, what) => write!(
70 f,
71 "`{file}`'s default names `{what}`, which settles to no one number; a default \
72 resolves with no invocation behind it, so a signal is what a caller passes"
73 ),
74 }
75 }
76}
77
78#[derive(Clone, Debug, PartialEq, Eq, Default)]
81pub struct Located {
82 pub node: String,
83 pub span: Option<ByteSpan>,
84}
85
86impl Located {
87 pub fn at(node: impl Into<String>, span: Option<ByteSpan>) -> Located {
88 Located {
89 node: node.into(),
90 span,
91 }
92 }
93}
94
95impl fmt::Display for Located {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self.span {
98 Some(span) => write!(f, "{}:{}", self.node, span.start),
99 None => write!(f, "{}", self.node),
100 }
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct Diagnostic {
107 pub code: String,
108 pub message: String,
109 pub location: Located,
110 pub help: String,
111}
112
113impl fmt::Display for Diagnostic {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "{} ({}). {}", self.message, self.location, self.help)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq)]
120pub enum EngineError {
121 Binding {
122 node: String,
123 span: Option<ByteSpan>,
124 fault: BindingFault,
125 },
126 UnknownNode(String),
127 AmbiguousNode(String, Vec<String>),
128 RefAboveRoot(String, String),
129 UnknownBuiltin(String),
130 BadArity(String),
131 Refused(Box<Diagnostic>),
133 UnresolvedBars(Located),
135}
136
137impl EngineError {
138 pub fn code(&self) -> &str {
139 match self {
140 EngineError::Binding { fault, .. } => fault.code(),
141 EngineError::UnknownNode(_) => "engine.unknown_node",
142 EngineError::AmbiguousNode(..) => "engine.ambiguous_node",
143 EngineError::RefAboveRoot(..) => "engine.ref_above_root",
144 EngineError::UnknownBuiltin(_) => "engine.unknown_builtin",
145 EngineError::BadArity(_) => "engine.bad_arity",
146 EngineError::Refused(d) => &d.code,
147 EngineError::UnresolvedBars(_) => "engine.unresolved_bar_literal",
148 }
149 }
150
151 pub fn at(&self) -> Option<&Located> {
153 match self {
154 EngineError::Refused(d) => Some(&d.location),
155 EngineError::UnresolvedBars(at) => Some(at),
156 _ => None,
157 }
158 }
159
160 pub fn refused(d: Diagnostic) -> EngineError {
161 EngineError::Refused(Box::new(d))
162 }
163
164 pub fn of_closed_form(r: &Refusal, at: Located, help: impl Into<String>) -> EngineError {
167 EngineError::refused(Diagnostic {
168 code: r.code.as_str().to_string(),
169 message: r.message.clone(),
170 location: at,
171 help: help.into(),
172 })
173 }
174}
175
176impl fmt::Display for EngineError {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 EngineError::Binding { node, fault, .. } => write!(f, "`{node}`: {fault}"),
180 EngineError::UnknownNode(p) => write!(f, "no such node: `{p}`"),
181 EngineError::AmbiguousNode(file, names) => write!(
182 f,
183 "`{file}` is a file with {} instances; name one: {}",
184 names.len(),
185 names.join(", ")
186 ),
187 EngineError::RefAboveRoot(node, path) => {
188 write!(f, "`@{path}` in `{node}` walks above the composition root")
189 }
190 EngineError::UnknownBuiltin(n) => write!(
191 f,
192 "unresolved function `{n}` (no builtin or sugar implementation)"
193 ),
194 EngineError::BadArity(n) => write!(f, "wrong argument shape for `{n}`"),
195 EngineError::Refused(d) => write!(f, "{d}"),
196 EngineError::UnresolvedBars(at) => write!(
197 f,
198 "`{at}` holds a bar literal and this composition declares no bpm/meter"
199 ),
200 }
201 }
202}
203
204impl std::error::Error for EngineError {}
205
206pub static REGISTRY: [(&str, &str); 77] = [
209 (
210 "type.no_overload",
211 "a builtin applied to operand types no row names",
212 ),
213 ("type.domain_mismatch", "a closed form in t meets one in f"),
214 (
215 "type.samples_in_closed_form",
216 "a closed form meets samples or frames",
217 ),
218 (
219 "type.rate_conflict",
220 "two sampled operands at different rates",
221 ),
222 (
223 "type.frame_mismatch",
224 "two framed operands at different windows or hops",
225 ),
226 ("type.width_mismatch", "widths differ and neither is one"),
227 (
228 "type.filter_needs_a_dual",
229 "a filter needs a closed form with a dual, and this one has none",
230 ),
231 (
232 "type.non_affine_singular",
233 "delta or pv with a non-affine argument",
234 ),
235 (
236 "type.series_not_summable",
237 "a sum to inf whose dual does not converge",
238 ),
239 (
240 "type.self_gain_unbounded",
241 "a linear self whose loop gain does not settle",
242 ),
243 (
244 "type.bars_in_frequency",
245 "a bar unit inside a closed form in f",
246 ),
247 (
248 "type.non_integer_power",
249 "a real exponent over a base that is not a positive constant",
250 ),
251 ("cast.left_algebra", "fourier or ifourier on a non-pair"),
252 (
253 "cast.samples_are_terminal",
254 "fourier or ifourier on samples or frames",
255 ),
256 ("cast.stft_needs_samples", "stft on anything but samples"),
257 ("cast.istft_needs_frames", "istft on anything but frames"),
258 ("cast.missing_window", "stft without window= or hop="),
259 (
260 "collapse.no_horizon",
261 "a collapse with no window to run against",
262 ),
263 (
264 "collapse.empty_band",
265 "every line sits at or above the ceiling",
266 ),
267 (
268 "collapse.singular_in_ct",
269 "a delta or pv atom reaches point sampling",
270 ),
271 (
272 "collapse.not_evaluable",
273 "a subterm with no value at a point",
274 ),
275 (
276 "collapse.series_nesting",
277 "series nested past what one instant expands",
278 ),
279 (
280 "collapse.no_block_row",
281 "a closed form in f read span by span, which one transform over a horizon places",
282 ),
283 (
284 "collapse.over_budget",
285 "a render counting more operations than the caller allowed",
286 ),
287 (
288 "samples.zero_delay_loop",
289 "a sampled loop reads no written sample",
290 ),
291 (
292 "samples.hop_outside_cola",
293 "an stft hop the window does not overlap-add across",
294 ),
295 (
296 "samples.window_not_power_of_two",
297 "an stft window that is not a power of two",
298 ),
299 (
300 "samples.channel_out_of_range",
301 "a component past the width of the value it was asked of",
302 ),
303 (
304 "samples.grid_too_large",
305 "a finite-difference grid past what one call may hold at this rate",
306 ),
307 (
308 "samples.string_past_rate",
309 "a string whose first two partials no stable grid rings at this rate",
310 ),
311 (
312 "samples.bridge_unstable",
313 "a unison whose bridge leaves its scheme not provably stable at this rate",
314 ),
315 (
316 "samples.contact_unsettled",
317 "a contact force whose solve reached no value at some sample",
318 ),
319 (
320 "samples.state_mismatch",
321 "a held state handed to call sites other than the ones it was held for",
322 ),
323 (
324 "grammar.unknown_name",
325 "an identifier that is no builtin, unit or note name",
326 ),
327 ("grammar.arity", "a call with the wrong argument count"),
328 (
329 "grammar.unknown_named_argument",
330 "a named argument the builtin does not read",
331 ),
332 ("grammar.inf_out_of_place", "inf outside a sum bound"),
333 (
334 "ref.fractional_shift_on_samples",
335 "a sampled read offset that is not a whole sample",
336 ),
337 (
338 "engine.unknown_node",
339 "a target no node of this composition answers for",
340 ),
341 (
342 "engine.ambiguous_node",
343 "a path more than one node answers for",
344 ),
345 (
346 "engine.ref_above_root",
347 "a ref reaching outside the composition",
348 ),
349 ("engine.unknown_builtin", "a call no builtin table names"),
350 (
351 "engine.physics_out_of_range",
352 "a finite-difference argument outside the range its model admits",
353 ),
354 (
355 "engine.bad_arity",
356 "a builtin whose own lowering wanted other arguments",
357 ),
358 (
359 "engine.unresolved_bar_literal",
360 "a bar literal with no bpm and meter behind it",
361 ),
362 (
363 "engine.unbound_variable",
364 "a body reading a name no invocation bound",
365 ),
366 ("engine.unused_argument", "an argument the body never reads"),
367 (
368 "engine.reserved_parameter",
369 "a parameter named t, f, pi, i or self",
370 ),
371 ("engine.duplicate_argument", "one argument given twice"),
372 ("engine.self_in_argument", "self written inside an argument"),
373 (
374 "engine.shifted_parameter_read",
375 "a parameter read at a shifted time",
376 ),
377 (
378 "engine.too_many_instances",
379 "more argument tuples than one node may hold",
380 ),
381 (
382 "engine.default_reads_buffer",
383 "a default naming what settles to no one number",
384 ),
385 (
386 "engine.cyclic_substitution",
387 "a closed form reaching itself around a loop of refs",
388 ),
389 (
390 "read.no_spectral_sum",
391 "an exact reading of a term no atom sum reaches",
392 ),
393 (
394 "read.series_not_enumerable",
395 "a line reading of a series whose term is no line",
396 ),
397 (
398 "read.lines_need_unwindowed_lines",
399 "a line reading of a term a window gave a width",
400 ),
401 (
402 "engine.bad_crop_shoulder",
403 "a crop shoulder outside the window it belongs to",
404 ),
405 (
406 "engine.non_constant_argument",
407 "a number-valued argument that moves",
408 ),
409 (
410 "engine.varying_delay",
411 "a delay with no whole-sample offset on the grid",
412 ),
413 (
414 "engine.unreadable_shift",
415 "a ref read at a time this engine cannot settle",
416 ),
417 (
418 "engine.per_lane_read_on_samples",
419 "a buffer read at one time per component",
420 ),
421 (
422 "engine.forward_self_read",
423 "a self-reference reaching forward in time",
424 ),
425 (
426 "engine.series_body_not_inlinable",
427 "a closed loop whose body holds a value no term carries",
428 ),
429 (
430 "engine.unshadered_operation",
431 "an operation the shader builder has no arm for",
432 ),
433 (
434 "engine.not_materialized",
435 "a reading off samples this render never held",
436 ),
437 (
438 "engine.observation_needs_samples",
439 "a sample-consuming reading asked of a closed form",
440 ),
441 (
442 "engine.observation_needs_a_graph",
443 "a reading asked of a buffer with no node behind it",
444 ),
445 (
446 "engine.observation_not_wired",
447 "a reading no buffer answers",
448 ),
449 (
450 "engine.alias_needs_a_closed_form",
451 "an alias score asked of samples with no closed form behind them",
452 ),
453 (
454 "engine.release_not_causal",
455 "a sample before key-up that would depend on release",
456 ),
457 (
458 "engine.no_stream",
459 "a stream over a node no block reads alone, or a target or binding it cannot open",
460 ),
461 (
462 "engine.binding_not_causal",
463 "a binding moved at a checkpoint where a sample before it could hear the move",
464 ),
465 (
466 "engine.checkpoint_mismatch",
467 "a checkpoint resumed on a stream or node other than the one it was taken of",
468 ),
469 (
470 "engine.never_silent",
471 "a render until silent of a node that holds a level forever",
472 ),
473 (
474 "engine.not_silent_by",
475 "a render until silent whose bound is not under the floor by the latest time",
476 ),
477 (
478 "engine.no_tail_bound",
479 "a render until silent through a node class no tail bound is derived for",
480 ),
481];