Skip to main content

sva_engine/
cast.rs

1// Concern: the five written casts, the type each produces and the mismatch it writes | Non-concern: performing the approximation (sva-samples) | IO: (Cast, &[Ty]) -> Ty or Mismatch
2
3use sva_formula::{Codomain, Held, Origin, Ty, Var};
4
5/// The operand types, the subterm that blocked, and the cast to write.
6#[derive(Clone, Debug, PartialEq)]
7pub struct Mismatch {
8    pub code: &'static str,
9    pub got: Vec<Ty>,
10    pub blocker: Option<(Origin, String)>,
11    pub repair: String,
12}
13
14impl Mismatch {
15    pub fn new(code: &'static str, got: &[Ty], repair: impl Into<String>) -> Mismatch {
16        Mismatch {
17            code,
18            got: got.to_vec(),
19            blocker: None,
20            repair: repair.into(),
21        }
22    }
23
24    pub fn blocked_by(mut self, origin: Origin, what: impl Into<String>) -> Mismatch {
25        self.blocker = Some((origin, what.into()));
26        self
27    }
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum Cast {
32    Sample,
33    Fourier,
34    IFourier,
35    Stft { window: usize, hop: usize },
36    Istft,
37}
38
39impl Cast {
40    pub const NAMES: [&'static str; 5] = sva_ast::CASTS;
41
42    /// A short-time transform's window and hop are counted where the call is lowered.
43    pub fn from_name(name: &str) -> Option<Cast> {
44        Some(match name {
45            "sample" => Cast::Sample,
46            "fourier" => Cast::Fourier,
47            "ifourier" => Cast::IFourier,
48            "stft" => Cast::Stft { window: 0, hop: 0 },
49            "istft" => Cast::Istft,
50            _ => return None,
51        })
52    }
53
54    pub fn name(self) -> &'static str {
55        match self {
56            Cast::Sample => "sample",
57            Cast::Fourier => "fourier",
58            Cast::IFourier => "ifourier",
59            Cast::Stft { .. } => "stft",
60            Cast::Istft => "istft",
61        }
62    }
63
64    /// A window and a hop belong to the cast, so they are checked where the call is written.
65    pub fn check_arguments(self) -> Result<(), Mismatch> {
66        match self {
67            Cast::Stft { window, hop } if window == 0 || hop == 0 => Err(Mismatch::new(
68                "cast.missing_window",
69                &[],
70                "stft needs window= and hop=",
71            )),
72            _ => Ok(()),
73        }
74    }
75
76    pub fn resolve(self, args: &[Ty]) -> Result<Ty, Mismatch> {
77        let [arg] = args else {
78            return Err(Mismatch::new(
79                "grammar.arity",
80                args,
81                format!("`{}` takes one signal", self.name()),
82            ));
83        };
84        match self {
85            Cast::Fourier => self.project(*arg, Var::F, args),
86            Cast::IFourier => self.project(*arg, Var::T, args),
87            Cast::Sample => match arg.held {
88                Held::Form(Var::T) => Ok(sampled(arg)),
89                Held::Form(Var::F) if arg.has_dual() => Ok(sampled(arg)),
90                Held::Form(Var::F) => Err(Mismatch::new(
91                    "cast.left_algebra",
92                    args,
93                    "write sample(ifourier(x)) to sample a spectrum in t",
94                )),
95                Held::Sampled => Ok(*arg),
96                Held::Frames => Err(Mismatch::new(
97                    "type.samples_in_closed_form",
98                    args,
99                    "this is frames; write istft(...) to read them as samples",
100                )),
101            },
102            Cast::Stft { .. } => match arg.held {
103                Held::Sampled => Ok(Ty {
104                    held: Held::Frames,
105                    ..*arg
106                }),
107                Held::Frames => Err(Mismatch::new(
108                    "cast.stft_needs_samples",
109                    args,
110                    "this is already frames; drop the stft(...)",
111                )),
112                _ => Err(Mismatch::new(
113                    "cast.stft_needs_samples",
114                    args,
115                    "stft reads samples. write stft(sample(x), window=, hop=)",
116                )),
117            },
118            Cast::Istft => match arg.held {
119                Held::Frames => Ok(sampled(arg)),
120                _ => Err(Mismatch::new(
121                    "cast.istft_needs_frames",
122                    args,
123                    "istft reads frames. write istft(stft(x, window=, hop=))",
124                )),
125            },
126        }
127    }
128
129    /// A retyping cast selects the other axis of a value that has both, and changes nothing:
130    /// what it states is the axis the expression above it is written on.
131    fn project(self, arg: Ty, onto: Var, args: &[Ty]) -> Result<Ty, Mismatch> {
132        match arg.held {
133            Held::Form(_) if arg.has_dual() => Ok(Ty {
134                held: Held::Form(onto),
135                codomain: match onto {
136                    Var::F => Codomain::Complex,
137                    Var::T => arg.codomain,
138                },
139                ..arg
140            }),
141            Held::Sampled | Held::Frames => Err(Mismatch::new(
142                "cast.samples_are_terminal",
143                args,
144                "nothing returns from samples to a closed form",
145            )),
146            _ => Err(Mismatch::new(
147                "cast.left_algebra",
148                args,
149                format!(
150                    "write {}(sample(x)) for a short-time spectrum, labeled measured",
151                    self.name()
152                ),
153            )),
154        }
155    }
156}
157
158fn sampled(arg: &Ty) -> Ty {
159    Ty {
160        held: Held::Sampled,
161        dual: false,
162        ..*arg
163    }
164}