1use std::sync::atomic::{AtomicU32, Ordering};
4
5use sva_ast::{Address, Arg, BinOp, ByteSpan, Expr, Literal};
6use sva_samples::Extent;
7
8use crate::typing::{SumSlot, Typing};
9
10pub const NOTES: &str = "notes";
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct Handle(pub u32);
15
16impl Handle {
17 pub(super) fn node(self) -> String {
18 format!("{NOTES}#{}", self.0)
19 }
20}
21
22static HANDLES: AtomicU32 = AtomicU32::new(0);
24
25#[derive(Clone)]
26struct Term {
27 handle: Handle,
28 expr: Expr,
29 addressed: bool,
31 landed: i64,
32}
33
34impl Term {
35 fn holds(&self, handle: Handle) -> bool {
36 self.addressed && self.handle == handle
37 }
38}
39
40#[derive(Clone, Default)]
41pub(super) struct Terms {
42 terms: Vec<Term>,
43 retired: Option<Extent>,
45}
46
47impl Terms {
48 pub(super) fn count(&self) -> usize {
49 self.terms.len()
50 }
51
52 pub(super) fn is_empty(&self) -> bool {
53 self.terms.is_empty()
54 }
55
56 pub(super) fn exprs(&self) -> impl Iterator<Item = &Expr> {
57 self.terms.iter().map(|t| &t.expr)
58 }
59
60 pub(super) fn nodes(&self) -> impl Iterator<Item = (String, Expr)> {
61 self.terms.iter().map(|t| (t.handle.node(), t.expr.clone()))
62 }
63
64 pub(super) fn handles(&self) -> impl Iterator<Item = Handle> {
65 self.terms.iter().map(|t| t.handle)
66 }
67
68 pub(super) fn added(&self, expr: Expr) -> (Terms, Handle) {
69 let mut next = self.clone();
70 let handle = Handle(HANDLES.fetch_add(1, Ordering::Relaxed));
71 next.terms.push(Term {
72 handle,
73 expr,
74 addressed: true,
75 landed: 0,
76 });
77 (next, handle)
78 }
79
80 fn held(&mut self, handle: Handle) -> Option<&mut Term> {
81 self.terms.iter_mut().find(|t| t.holds(handle))
82 }
83
84 pub(super) fn landed(&self, handle: Handle) -> Option<i64> {
85 Some(self.terms.iter().find(|t| t.holds(handle))?.landed)
86 }
87
88 pub(super) fn land(&mut self, handle: Handle, at: i64) {
89 if let Some(term) = self.held(handle) {
90 term.landed = at;
91 }
92 }
93
94 pub(super) fn replaced(&self, handle: Handle, expr: impl FnOnce(i64) -> Expr) -> Option<Terms> {
96 let mut next = self.clone();
97 let term = next.held(handle)?;
98 term.expr = expr(term.landed);
99 Some(next)
100 }
101
102 pub(super) fn removed(&self, handle: Handle, at: f64) -> Option<Terms> {
104 let mut next = self.clone();
105 let term = next.held(handle)?;
106 let never = Expr::Bin(
107 BinOp::Sub,
108 Box::new(Expr::Lit(Literal::Num(0.0))),
109 Box::new(Expr::Var("inf".to_string())),
110 );
111 term.expr = Expr::Call {
112 name: "crop".to_string(),
113 args: vec![
114 Arg::Pos(term.expr.clone()),
115 Arg::Pos(never),
116 Arg::Pos(Expr::Lit(Literal::Num(at))),
117 ],
118 span: SPAN,
119 };
120 term.addressed = false;
121 Some(next)
122 }
123
124 pub(super) fn sum(&self) -> Expr {
125 self.terms
126 .iter()
127 .map(|t| Expr::Ref {
128 path: t.handle.node(),
129 arg: Box::new(Expr::Var("t".to_string())),
130 binds: Vec::new(),
131 address: Address::Time,
132 span: SPAN,
133 })
134 .reduce(|sum, t| Expr::Bin(BinOp::Add, Box::new(sum), Box::new(t)))
135 .unwrap_or(Expr::Lit(Literal::Num(0.0)))
136 }
137
138 pub(super) fn name(&self, tys: &mut Typing) {
140 let Some(notes) = tys.id(NOTES) else {
141 return;
142 };
143 let live = self.terms.iter();
144 let live = live.map(|t| tys.id(&t.handle.node()).map(SumSlot::Node));
145 let retired = self.retired.map(|support| Some(SumSlot::Retired(support)));
146 if let Some(slots) = live.chain(retired).collect::<Option<Vec<_>>>() {
147 tys.name_sum(notes, slots);
148 }
149 }
150
151 pub(super) fn prune(
154 &mut self,
155 gone: &dyn Fn(Handle) -> bool,
156 support: &dyn Fn(Handle) -> Option<Extent>,
157 ) -> bool {
158 let live = self.terms.len();
159 let (went, kept): (Vec<Term>, _) = std::mem::take(&mut self.terms)
160 .into_iter()
161 .partition(|t| gone(t.handle));
162 self.terms = kept;
163 for ended in went.iter().filter_map(|t| support(t.handle)) {
164 self.retired = Some(self.retired.map_or(ended, |hull| hull.hull(ended)));
165 }
166 self.terms.len() != live
167 }
168}
169
170const SPAN: ByteSpan = ByteSpan { start: 0, end: 0 };
171
172pub(super) fn placed(expr: &Expr, at: i64) -> Expr {
174 let moved = |e: &Expr| Box::new(placed(e, at));
175 match expr {
176 Expr::Var(name) if name == "t" && at != 0 => Expr::Bin(
177 BinOp::Sub,
178 Box::new(expr.clone()),
179 Box::new(Expr::Lit(Literal::Samples(at as f64))),
180 ),
181 Expr::Lit(_) | Expr::Var(_) => expr.clone(),
182 Expr::Bin(op, l, r) => Expr::Bin(*op, moved(l), moved(r)),
183 Expr::Call { name, args, span } => Expr::Call {
184 name: name.clone(),
185 args: args
186 .iter()
187 .map(|arg| match arg {
188 Arg::Pos(e) => Arg::Pos(placed(e, at)),
189 Arg::Named(n, e) => Arg::Named(n.clone(), placed(e, at)),
190 })
191 .collect(),
192 span: *span,
193 },
194 Expr::Ref {
195 path,
196 arg,
197 binds,
198 address,
199 span,
200 } => Expr::Ref {
201 path: path.clone(),
202 arg: moved(arg),
203 binds: binds
204 .iter()
205 .map(|(n, e)| (n.clone(), placed(e, at)))
206 .collect(),
207 address: *address,
208 span: *span,
209 },
210 Expr::SelfRef { arg, address, span } => Expr::SelfRef {
211 arg: moved(arg),
212 address: *address,
213 span: *span,
214 },
215 Expr::Indexed { name, arg, span } => Expr::Indexed {
216 name: name.clone(),
217 arg: moved(arg),
218 span: *span,
219 },
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
230 fn notes_is_named_by_its_sounding_terms_and_one_retired_slot() {
231 let mut terms = Terms::default();
232 let mut last = None;
233 for k in 0..1_000 {
234 let (next, handle) = terms.added(Expr::Lit(Literal::Num(1.0)));
235 terms = next;
236 let ended = |h: Handle| Some(h) == last;
237 assert_eq!(terms.prune(&ended, &|_| Some(Extent::new(k, k + 1))), k > 0);
238 last = Some(handle);
239 }
240 let dir = std::env::temp_dir().join(format!("sva-terms-{}", std::process::id()));
241 std::fs::create_dir_all(&dir).expect("a directory");
242 std::fs::write(dir.join("one"), "1\n").expect("a node file");
243 let mut graph = sva_ast::parse_composition(&dir).expect("a composition");
244 for (name, body) in std::iter::once((NOTES.to_string(), terms.sum())).chain(terms.nodes()) {
245 assert!(graph.define(&name, body));
246 }
247 let mut tys = crate::types(&graph, NOTES).expect("typed");
248 terms.name(&mut tys);
249 let (notes, sounding) = (tys.id(NOTES), last.and_then(|h| tys.id(&h.node())));
250 let slots = notes.and_then(|notes| tys.sum_slots(notes)).expect("named");
251 let sounding = SumSlot::Node(sounding.expect("the last term"));
252 assert_eq!(slots, [sounding, SumSlot::Retired(Extent::new(1, 1_000))]);
253 }
254}