1use sva_formula::{Codomain, Held, Origin, Ty, Var};
4
5#[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 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 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 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}