Skip to main content

sva_engine/
overload.rs

1// Concern: the signature of every builtin and operator, and the one refusal a call writes | Non-concern: evaluating them, the named-argument tables (vocabulary.rs) | IO: (name, &[Ty]) -> Ty
2
3use 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/// `join` takes two to `MAX_WIDTH` components; a knot table takes as many knots as it holds.
25#[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    /// The most positionals it takes past its own, where it takes more.
36    pub tail: Tail,
37    pub result: fn(&[Ty]) -> Result<Ty, Mismatch>,
38}
39
40impl Signature {
41    /// One table per name, held where the CLI already prints it from.
42    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    /// `None` where no count is too many.
51    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
136/// Overload resolution knows no axis of its own; `lower::walk` reads the answer onto the
137/// node's own before it becomes a type.
138fn 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
162/// The meet of FORMAT 8.2, which is what every elementwise name resolves by.
163fn 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
186/// A closed form keeps its representation and `sva_formula::infer` decides which one; only
187/// the closed-form-versus-samples split is settled here.
188fn 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
208/// One real value whatever its time reads; the knots are numbers.
209fn 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
247/// The window and hop a call wrote are not in a `Ty`, so the table types the representation
248/// and the call site checks the arguments.
249fn 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
352/// Every operand in the order written. `Ty` has no scalar of its own, so a constant arrives
353/// as the representation of the signal it meets, neutral by FORMAT 3.3's `Sc, S -> S` row,
354/// under its own width.
355pub 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
366/// How many arguments a name takes, checked against the call as written. A signal is
367/// positional, a required parameter is answered once, and an unread key is refused.
368pub 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
429/// FORMAT 3.1's notation column, which a table prints where a sentence has no room.
430pub 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
441/// FORMAT 3.1's name column, read out: the same types `notation` spells, in a sentence.
442pub 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
462/// The one producer of the three-part shape of FORMAT 7.1 and the operand-then-repair shape
463/// of 8.3: every code of section 16 reaches a diagnostic through here.
464pub 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}