1use sva_formula::{Codomain, Held, MAX_WIDTH, Mismatch as MeetMismatch, Ty, Var};
4
5use crate::cast::{Cast, Mismatch};
6use crate::error::{Diagnostic, Located};
7use crate::vocabulary::recognized_named;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum ParamKind {
11 Signal,
12 Scalar,
13 Index,
14 Bound,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct Param {
19 pub name: &'static str,
20 pub required: bool,
21 pub kind: ParamKind,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Tail {
27 Fixed,
28 Upto(usize),
29 Open,
30}
31
32pub struct Signature {
33 pub name: &'static str,
34 pub params: &'static [Param],
35 pub tail: Tail,
37 pub result: fn(&[Ty]) -> Result<Ty, Mismatch>,
38}
39
40impl Signature {
41 pub fn named(&self) -> &'static [&'static str] {
43 recognized_named(self.name).unwrap_or(&[])
44 }
45
46 pub fn required(&self) -> usize {
47 self.params.iter().filter(|p| p.required).count()
48 }
49
50 pub fn most(&self) -> Option<usize> {
52 match self.tail {
53 Tail::Fixed => Some(self.params.len()),
54 Tail::Upto(n) => Some(n),
55 Tail::Open => None,
56 }
57 }
58}
59
60const fn need(name: &'static str, kind: ParamKind) -> Param {
61 Param {
62 name,
63 required: true,
64 kind,
65 }
66}
67
68const fn opt(name: &'static str, kind: ParamKind) -> Param {
69 Param {
70 name,
71 required: false,
72 kind,
73 }
74}
75
76const SIGNAL: &[Param] = &[need("x", ParamKind::Signal)];
77const TWO: &[Param] = &[need("a", ParamKind::Signal), need("b", ParamKind::Signal)];
78const WAVE: &[Param] = &[
79 need("hz", ParamKind::Scalar),
80 opt("phase", ParamKind::Scalar),
81];
82const CROP: &[Param] = &[
83 need("x", ParamKind::Signal),
84 need("start", ParamKind::Scalar),
85 need("end", ParamKind::Scalar),
86];
87const DRIVEN: &[Param] = &[
88 need("x", ParamKind::Signal),
89 opt("drive", ParamKind::Scalar),
90];
91const RAND: &[Param] = &[
92 need("key", ParamKind::Signal),
93 opt("seed", ParamKind::Scalar),
94];
95const SERIES_PARAMS: &[Param] = &[
96 need("k", ParamKind::Index),
97 need("lo", ParamKind::Bound),
98 need("hi", ParamKind::Bound),
99 need("term", ParamKind::Signal),
100];
101const KNOTTED: &[Param] = &[
102 need("at", ParamKind::Signal),
103 need("t0", ParamKind::Scalar),
104 need("v0", ParamKind::Scalar),
105 need("t1", ParamKind::Scalar),
106 need("v1", ParamKind::Scalar),
107];
108const CHANNEL_PARAMS: &[Param] = &[need("x", ParamKind::Signal), need("k", ParamKind::Index)];
109const FUNDAMENTAL: &[Param] = &[need("f0", ParamKind::Scalar)];
110const SEED: &[Param] = &[need("seed", ParamKind::Scalar)];
111const LENGTH: &[Param] = &[need("length", ParamKind::Scalar)];
112const VELOCITY: &[Param] = &[need("vel", ParamKind::Scalar)];
113const VOLUME: &[Param] = &[need("volume", ParamKind::Scalar)];
114const RECTANGLE: &[Param] = &[need("lx", ParamKind::Scalar), opt("ly", ParamKind::Scalar)];
115const BOX_SIDES: &[Param] = &[
116 need("lx", ParamKind::Scalar),
117 opt("ly", ParamKind::Scalar),
118 opt("lz", ParamKind::Scalar),
119];
120const FILTER_ONE_POLE: &[Param] = &[
121 need("x", ParamKind::Signal),
122 need("cutoff", ParamKind::Scalar),
123];
124const FILTER_Q: &[Param] = &[
125 need("x", ParamKind::Signal),
126 need("cutoff", ParamKind::Scalar),
127 opt("q", ParamKind::Scalar),
128];
129const FILTER_GAIN: &[Param] = &[
130 need("x", ParamKind::Signal),
131 need("cutoff", ParamKind::Scalar),
132 opt("q", ParamKind::Scalar),
133 opt("gain", ParamKind::Scalar),
134];
135
136fn neutral() -> Ty {
139 Ty::form(Var::T, true, Codomain::Real)
140}
141
142fn fault(m: MeetMismatch, args: &[Ty]) -> Mismatch {
143 let (code, repair) = match m {
144 MeetMismatch::Domain => (
145 "type.domain_mismatch",
146 "write ifourier on the f side to work in t, or fourier on the t side to work in f",
147 ),
148 MeetMismatch::SamplesInClosedForm => (
149 "type.samples_in_closed_form",
150 "write sample(...) on the closed form to move the whole expression into samples",
151 ),
152 MeetMismatch::Rate => ("type.rate_conflict", "one rate per expression"),
153 MeetMismatch::Frames => ("type.frame_mismatch", "one window and hop per expression"),
154 MeetMismatch::Width => (
155 "type.width_mismatch",
156 "give both sides the same width, or make one of them mono",
157 ),
158 };
159 Mismatch::new(code, args, repair)
160}
161
162fn meet(args: &[Ty]) -> Result<Ty, Mismatch> {
164 let Some((first, rest)) = args.split_first() else {
165 return Ok(neutral());
166 };
167 let mut acc = *first;
168 for ty in rest {
169 acc = acc.meet(*ty).map_err(|m| fault(m, args))?;
170 }
171 Ok(acc)
172}
173
174fn scalar(_args: &[Ty]) -> Result<Ty, Mismatch> {
175 Ok(neutral())
176}
177
178fn dual_form(_args: &[Ty]) -> Result<Ty, Mismatch> {
179 Ok(neutral())
180}
181
182fn samples(_args: &[Ty]) -> Result<Ty, Mismatch> {
183 Ok(Ty::discrete(Held::Sampled, Codomain::Real))
184}
185
186fn elementwise(args: &[Ty]) -> Result<Ty, Mismatch> {
189 meet(args)
190}
191
192fn filter(args: &[Ty]) -> Result<Ty, Mismatch> {
193 let signal = args.first().copied().unwrap_or_else(neutral);
194 match signal.has_dual() || signal.held == Held::Sampled {
195 true => meet(std::slice::from_ref(&signal)),
196 false => Err(Mismatch::new(
197 "type.filter_needs_a_dual",
198 args,
199 "write the filter over sample(x) to filter at the render rate",
200 )),
201 }
202}
203
204fn series(args: &[Ty]) -> Result<Ty, Mismatch> {
205 Ok(args.get(3).copied().unwrap_or_else(neutral))
206}
207
208fn knotted(args: &[Ty]) -> Result<Ty, Mismatch> {
210 let at = meet(&args[..args.len().min(1)])?;
211 Ok(Ty {
212 width: 1,
213 codomain: Codomain::Real,
214 ..at
215 })
216}
217
218fn joined(args: &[Ty]) -> Result<Ty, Mismatch> {
219 let mut width = 0u32;
220 let mono: Vec<Ty> = args.iter().map(|t| Ty { width: 1, ..*t }).collect();
221 for ty in args {
222 width += u32::from(ty.width);
223 }
224 let acc = meet(&mono)?;
225 let Ok(width) = u8::try_from(width) else {
226 return Err(too_wide(args, width));
227 };
228 if width > MAX_WIDTH {
229 return Err(too_wide(args, u32::from(width)));
230 }
231 Ok(Ty { width, ..acc })
232}
233
234fn too_wide(args: &[Ty], width: u32) -> Mismatch {
235 Mismatch::new(
236 "type.width_mismatch",
237 args,
238 format!("{width} components join; a value carries at most {MAX_WIDTH}"),
239 )
240}
241
242fn channel(args: &[Ty]) -> Result<Ty, Mismatch> {
243 let x = args.first().copied().unwrap_or_else(neutral);
244 Ok(Ty { width: 1, ..x })
245}
246
247fn cast_of(name: &str, args: &[Ty]) -> Result<Ty, Mismatch> {
250 Cast::from_name(name)
251 .map(|cast| match cast {
252 Cast::Stft { .. } => Cast::Stft { window: 1, hop: 1 },
253 other => other,
254 })
255 .expect("a cast signature names a cast")
256 .resolve(args)
257}
258
259const fn plain(
260 name: &'static str,
261 params: &'static [Param],
262 result: fn(&[Ty]) -> Result<Ty, Mismatch>,
263) -> Signature {
264 Signature {
265 name,
266 params,
267 tail: Tail::Fixed,
268 result,
269 }
270}
271
272pub const OPERATORS: [&str; 5] = ["+", "-", "*", "/", "%"];
273
274pub static SIGNATURES: &[Signature] = &[
275 plain("+", TWO, elementwise),
276 plain("-", TWO, elementwise),
277 plain("*", TWO, elementwise),
278 plain("/", TWO, elementwise),
279 plain("%", TWO, elementwise),
280 plain("sin", SIGNAL, elementwise),
281 plain("cos", SIGNAL, elementwise),
282 plain("exp", SIGNAL, elementwise),
283 plain("log", SIGNAL, elementwise),
284 plain("sqrt", SIGNAL, elementwise),
285 plain("abs", SIGNAL, elementwise),
286 plain("tanh", SIGNAL, elementwise),
287 plain("step", SIGNAL, elementwise),
288 plain("sat", DRIVEN, elementwise),
289 plain("pow", TWO, elementwise),
290 plain("max", TWO, elementwise),
291 plain("min", TWO, elementwise),
292 plain("saw", WAVE, dual_form),
293 plain("square", WAVE, dual_form),
294 plain("triangle", WAVE, dual_form),
295 plain("crop", CROP, elementwise),
296 plain("noise", SEED, dual_form),
297 plain("string", FUNDAMENTAL, dual_form),
298 plain("membrane", RECTANGLE, dual_form),
299 plain("bar", LENGTH, dual_form),
300 plain("bore", LENGTH, dual_form),
301 plain("room", BOX_SIDES, dual_form),
302 plain("hammer_pulse", VELOCITY, dual_form),
303 plain("helmholtz", VOLUME, dual_form),
304 plain("rand", RAND, scalar),
305 plain("delta", SIGNAL, dual_form),
306 plain("pv", SIGNAL, dual_form),
307 plain("sum", SERIES_PARAMS, series),
308 Signature {
309 name: "join",
310 params: TWO,
311 tail: Tail::Upto(MAX_WIDTH as usize),
312 result: joined,
313 },
314 Signature {
315 name: sva_ast::PWL,
316 params: KNOTTED,
317 tail: Tail::Open,
318 result: knotted,
319 },
320 Signature {
321 name: sva_ast::PWL_INTEGRAL,
322 params: KNOTTED,
323 tail: Tail::Open,
324 result: knotted,
325 },
326 plain("ch", CHANNEL_PARAMS, channel),
327 plain("chaigne_askenfelt", FUNDAMENTAL, samples),
328 plain("willemsen_bilbao_serafin", FUNDAMENTAL, samples),
329 plain("darabundit_scavone", LENGTH, samples),
330 plain("rhaouti_chaigne_joly", FUNDAMENTAL, samples),
331 plain("chaigne_doutaut", FUNDAMENTAL, samples),
332 plain("botteldooren", FUNDAMENTAL, samples),
333 plain("lp", FILTER_ONE_POLE, filter),
334 plain("lowpass", FILTER_Q, filter),
335 plain("highpass", FILTER_Q, filter),
336 plain("bandpass", FILTER_Q, filter),
337 plain("notch", FILTER_Q, filter),
338 plain("peaking", FILTER_GAIN, filter),
339 plain("lowshelf", FILTER_GAIN, filter),
340 plain("highshelf", FILTER_GAIN, filter),
341 plain("sample", SIGNAL, |a| cast_of("sample", a)),
342 plain("fourier", SIGNAL, |a| cast_of("fourier", a)),
343 plain("ifourier", SIGNAL, |a| cast_of("ifourier", a)),
344 plain("stft", SIGNAL, |a| cast_of("stft", a)),
345 plain("istft", SIGNAL, |a| cast_of("istft", a)),
346];
347
348pub fn signature(name: &str) -> Option<&'static Signature> {
349 SIGNATURES.iter().find(|s| s.name == name)
350}
351
352pub fn resolve(name: &str, args: &[Ty]) -> Result<Ty, Mismatch> {
356 let Some(sig) = signature(name) else {
357 return Err(Mismatch::new(
358 "grammar.unknown_name",
359 args,
360 format!("`{name}` is not a builtin, a unit or a note name"),
361 ));
362 };
363 (sig.result)(args)
364}
365
366pub fn check_arity(name: &str, positional: usize, named: &[String]) -> Result<(), Mismatch> {
369 let Some(sig) = signature(name) else {
370 return Ok(());
371 };
372 let ceiling = sig.most().unwrap_or(usize::MAX);
373 for key in named {
374 let Some(at) = sig.params.iter().position(|p| p.name == *key) else {
375 if sig.named().contains(&key.as_str()) {
376 continue;
377 }
378 return Err(Mismatch::new(
379 "grammar.unknown_named_argument",
380 &[],
381 format!("`{name}` does not read `{key}`"),
382 ));
383 };
384 let param = sig.params[at];
385 if param.kind == ParamKind::Signal {
386 return Err(Mismatch::new(
387 "grammar.arity",
388 &[],
389 format!("give the signal `{name}` reads by position, not as `{key}=`"),
390 ));
391 }
392 if at < positional {
393 return Err(Mismatch::new(
394 "grammar.arity",
395 &[],
396 format!("give `{key}` to `{name}` by position or by name, not both"),
397 ));
398 }
399 }
400 if positional > ceiling {
401 return Err(Mismatch::new(
402 "grammar.arity",
403 &[],
404 format!("`{name}` takes {} arguments", sig.params.len()),
405 ));
406 }
407 match sig.params[positional.min(sig.params.len())..]
408 .iter()
409 .find(|p| p.required && !named.iter().any(|k| k == p.name))
410 {
411 None => Ok(()),
412 Some(p) if name == "rand" => Err(Mismatch::new(
413 "grammar.arity",
414 &[],
415 format!(
416 "a draw reads its `{}`, and a random source is a function of time: write \
417 `rand(t, seed=k)`, or a constant key for one fixed draw",
418 p.name
419 ),
420 )),
421 Some(p) => Err(Mismatch::new(
422 "grammar.arity",
423 &[],
424 format!("`{name}` reads `{}`, and none was written", p.name),
425 )),
426 }
427}
428
429pub fn notation(ty: Ty) -> &'static str {
431 match (ty.held, ty.has_dual()) {
432 (Held::Form(Var::T), false) => "Form(t)",
433 (Held::Form(Var::T), true) => "Form(t) dual",
434 (Held::Form(Var::F), false) => "Form(f)",
435 (Held::Form(Var::F), true) => "Form(f) dual",
436 (Held::Sampled, _) => "Sampled",
437 (Held::Frames, _) => "Frames",
438 }
439}
440
441pub fn describe(ty: Ty) -> String {
443 let word = match ty.held {
444 Held::Form(var) => format!(
445 "a closed form in `{}` with {} dual",
446 var.as_str(),
447 match ty.has_dual() {
448 true => "a",
449 false => "no",
450 }
451 ),
452 Held::Sampled => "samples".to_string(),
453 Held::Frames => "frames".to_string(),
454 };
455 match (ty.held, ty.rate, ty.width) {
456 (Held::Sampled, Some(rate), _) => format!("samples at {rate} Hz"),
457 (_, _, 1) => word,
458 (_, _, width) => format!("{word}, {width} components"),
459 }
460}
461
462pub fn format_refusal(
465 call: &str,
466 m: &Mismatch,
467 at: Located,
468 operands: &[(String, Ty)],
469) -> Diagnostic {
470 let types: Vec<String> = m.got.iter().map(|t| describe(*t)).collect();
471 let named: Vec<String> = operands
472 .iter()
473 .map(|(name, ty)| format!("{name}: {}", describe(*ty)))
474 .collect();
475 let head = match &m.blocker {
476 Some((_, what)) => {
477 format!("`{call}` refused: subterm {what} left A")
478 }
479 None => format!("`{call}` has no overload for ({}).", types.join(", ")),
480 };
481 let message = match named.is_empty() {
482 true => head,
483 false => format!("{head} {}.", named.join(". ")),
484 };
485 Diagnostic {
486 code: m.code.to_string(),
487 message,
488 location: at,
489 help: m.repair.clone(),
490 }
491}