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