1use sva_ast::outline::{Argument, Form};
4use sva_ast::score::{EXTENSION, Item, spelled};
5use sva_ast::song::{Curve, Held, Home, LaneId, Pos};
6use sva_ast::{Address, BinOp, ByteSpan, Literal, Outline, Refusal, SpanUnit};
7
8use crate::CliError;
9use crate::json::{NONE, escape, list, num};
10
11pub fn outline_data(text: &str) -> Result<String, CliError> {
14 if text.trim_start().starts_with('{') {
15 return score_data(text);
16 }
17 let tree = sva_ast::outline(text).map_err(|d| {
18 CliError::Refusals(vec![Refusal::new("expression", d.span, d.code, d.message)])
19 })?;
20 Ok(format!(
21 "{{ \"text\": \"{}\", \"outline\": {} }}",
22 escape(text),
23 node(&tree)
24 ))
25}
26
27fn time(amount: f64, unit: SpanUnit) -> String {
28 format!("\"{}\"", spelled(amount, unit))
29}
30
31fn score_data(text: &str) -> Result<String, CliError> {
32 let score = sva_ast::parse_score(text)
33 .map_err(|d| CliError::Refusals(vec![Refusal::new("score", d.span, d.code, d.message)]))?;
34 let unit = score.unit;
35 let mut items = Vec::new();
36 for line in &score.lines {
37 for item in &line.items {
38 let fields = match item {
39 Item::Event(e) => format!(
40 ", \"node\": \"@{}\", \"deg\": {}, \"len\": {}, \"args\": {}",
41 escape(&e.node.name),
42 e.deg
43 .map_or_else(|| NONE.to_string(), |(d, _)| d.to_string()),
44 e.len
45 .map_or_else(|| NONE.to_string(), |l| time(l.amount.to_f64(), unit)),
46 list(&e.binds, |b| format!(
47 "{{ \"name\": \"{}\", \"value\": \"{}\" }}",
48 escape(&b.name),
49 escape(&sva_ast::render_expr(&b.value))
50 ))
51 ),
52 Item::Mark(m) => format!(", \"mark\": \"{}\"", escape(&m.name)),
53 Item::Comment(_) => String::new(),
54 Item::Move(m) => format!(
55 ", \"node\": {}, \"lane\": \"{}\", \"to\": \"{}\", \"len\": {}, \"shape\": {}",
56 m.node
57 .as_ref()
58 .map_or_else(|| NONE.to_string(), |n| format!("\"@{}\"", escape(&n.name))),
59 escape(&m.lane.name),
60 escape(&m.to.text),
61 m.ramp
62 .map_or_else(|| NONE.to_string(), |r| time(r.len.amount.to_f64(), unit)),
63 m.ramp
64 .map_or_else(|| NONE.to_string(), |r| format!("\"{}\"", r.shape.name())),
65 ),
66 };
67 items.push(format!(
68 "{{ \"kind\": \"{}\", \"line\": {}, \"span\": {}, \"at\": {}{fields} }}",
69 item.kind(),
70 line.number,
71 span(line.span),
72 time(item.at().amount.to_f64(), unit)
73 ));
74 }
75 }
76 Ok(format!(
77 "{{ \"text\": \"{}\", \"score\": {} }}",
78 escape(text),
79 list(&items, String::clone)
80 ))
81}
82
83fn placed(at: Pos, curve: &Curve) -> String {
85 format!(
86 "{{ \"bars\": {}, \"seconds\": {} }}",
87 num(at.bars(curve)),
88 num(at.seconds(curve))
89 )
90}
91
92pub fn song_data(source: &dyn sva_ast::Source, target: &str) -> Result<String, CliError> {
95 let (graph, song) = crate::song_of(source, target)?;
96 let curve = song.curve();
97 let marks = list(song.marks(), |m| {
98 format!(
99 "{{ \"name\": \"{}\", \"at\": {}, \"file\": \"{}{EXTENSION}\", \"line\": {} }}",
100 escape(&m.name),
101 placed(m.at, curve),
102 escape(&m.score),
103 m.line
104 )
105 });
106 let tempo = list(&curve.pieces(), |(from, to, start, end, a, b)| {
107 format!(
108 "{{ \"bars\": [{}, {}], \"seconds\": [{}, {}], \"bpm\": [{}, {}] }}",
109 num(*from),
110 num(*to),
111 num(*start),
112 num(*end),
113 num(*a),
114 num(*b)
115 )
116 });
117 let instances = song.instances();
118 let lanes = list(song.lanes(), |lane| {
119 let reaching = instances
120 .iter()
121 .filter(|i| i.lanes.contains(&lane.id))
122 .count();
123 let moving = song.passed().iter().filter(|(_, l)| *l == lane.id).count();
124 let home = match &lane.home {
125 Home::Number(v, _) => num(*v),
126 Home::Scale(_) => "\"a scale\"".to_string(),
127 Home::Default(text) => format!("\"{}\"", escape(text)),
128 Home::None => NONE.to_string(),
129 };
130 let held = |h: &Option<Held>| match h {
131 Some(Held::Number(v)) => num(*v),
132 Some(Held::Scale(s)) => format!("\"{:?}\"", s.cents),
133 None => NONE.to_string(),
134 };
135 let moves = list(&lane.moves, |m| {
136 let r = &m.reached;
137 let line = graph.score(&r.score).map_or(0, |s| s.lines[r.line].number);
138 format!(
139 "{{ \"from\": {}, \"to\": {}, \"start\": {}, \"end\": {}, \"shape\": {}, \"file\": \"{}{EXTENSION}\", \"line\": {line}, \"chain\": \"{}\" }}",
140 held(&m.from),
141 held(&Some(m.to.clone())),
142 placed(r.start, curve),
143 placed(r.end.unwrap_or(r.start), curve),
144 m.shape
145 .map_or_else(|| NONE.to_string(), |s| format!("\"{}\"", s.name())),
146 escape(&r.score),
147 escape(&r.chain.text(curve))
148 )
149 });
150 format!(
151 "{{ \"lane\": \"{}\", \"home\": {home}, \"moves\": {moves}, \"instances\": {}, \"moving\": {moving} }}",
152 escape(&lane.id.name()),
153 reaching
154 )
155 });
156 let events = events(&graph, &song);
157 let typed = sva_engine::types(&graph, crate::PROBE).map_err(CliError::Engine)?;
158 Ok(format!(
159 "{{ \"song\": \"{}\", \"marks\": {marks}, \"tempo\": {tempo}, \"lanes\": {lanes}, \"events\": {}, \"size\": {{ \"events\": {}, \"instances\": {}, \"limit\": {} }} }}",
160 escape(target),
161 list(&events, String::clone),
162 events.len(),
163 typed.paths().count(),
164 sva_engine::instantiate::MAX_INSTANCES
165 ))
166}
167
168fn events(graph: &sva_ast::Graph, song: &sva_ast::song::Song) -> Vec<String> {
171 let curve = song.curve();
172 let source = graph.sourced();
173 song.instances()
174 .iter()
175 .filter_map(|i| Some((i, i.line.as_ref()?, i.at?, i.sounds(curve)?)))
176 .map(|(i, (score, li, ii), at, sounds)| {
177 let Some(Item::Event(e)) = graph.score(score).map(|s| &s.lines[*li].items[*ii]) else {
178 return String::new();
179 };
180 let hz = match e.deg {
181 Some((n, _)) => song
182 .key_at(at.seconds(curve))
183 .zip(song.scale_at(at.seconds(curve)).and_then(|s| s.ratio(n)))
184 .map(|(k, r)| k * r),
185 None => e
186 .binds
187 .iter()
188 .find(|b| b.name == "f0")
189 .and_then(|b| sva_ast::fold_number(&b.value, &|n| sva_ast::note_hz(n))),
190 };
191 let mut params = sva_ast::song::parameters(&source, &i.node);
192 params.sort();
193 let source = |p: &String| {
194 if i.lanes.contains(&LaneId::Sound(i.node.clone(), p.clone())) {
195 "lane"
196 } else if i.bound.contains(p) {
197 "line"
198 } else {
199 "default"
200 }
201 };
202 let len = song.ends(graph, i).map_or_else(
203 || NONE.to_string(),
204 |end| {
205 format!(
206 "{{ \"bars\": {}, \"seconds\": {} }}",
207 num(end.bars(curve) - at.bars(curve)),
208 num(end.seconds(curve) - at.seconds(curve))
209 )
210 },
211 );
212 format!(
213 "{{ \"at\": {}, \"sounds\": {}, \"len\": {len}, \"node\": \"{}\", \"hz\": {}, \"file\": \"{}{EXTENSION}\", \"line\": {}, \"params\": {} }}",
214 placed(at, curve),
215 placed(sounds, curve),
216 escape(&i.node),
217 hz.map_or_else(|| NONE.to_string(), num),
218 escape(score),
219 graph.score(score).map_or(0, |s| s.lines[*li].number),
220 list(¶ms, |p| format!(
221 "{{ \"name\": \"{}\", \"from\": \"{}\" }}",
222 escape(p),
223 source(p)
224 ))
225 )
226 })
227 .collect()
228}
229
230pub fn events_data(source: &dyn sva_ast::Source, target: &str) -> Result<String, CliError> {
233 let (graph, song) = crate::song_of(source, target)?;
234 Ok(format!(
235 "{{ \"song\": \"{}\", \"events\": {} }}",
236 escape(target),
237 list(&events(&graph, &song), String::clone)
238 ))
239}
240
241fn span(at: ByteSpan) -> String {
242 format!("{{ \"start\": {}, \"end\": {} }}", at.start, at.end)
243}
244
245fn node(o: &Outline) -> String {
246 let (kind, fields) = match &o.form {
247 Form::Literal(l) => ("literal", literal(l)),
248 Form::Name(name) => ("name", format!("\"name\": \"{}\"", escape(name))),
249 Form::Operator {
250 op,
251 at,
252 left,
253 right,
254 } => (
255 "operator",
256 format!(
257 "\"op\": \"{}\", \"at\": {}, \"left\": {}, \"right\": {}",
258 symbol(*op),
259 span(*at),
260 node(left),
261 node(right)
262 ),
263 ),
264 Form::Call { name, at, args } => (
265 "call",
266 format!(
267 "\"name\": \"{}\", \"at\": {}, \"args\": {}",
268 escape(name),
269 span(*at),
270 list(args, argument)
271 ),
272 ),
273 Form::Ref {
274 path,
275 at,
276 arg,
277 binds,
278 address,
279 } => (
280 "ref",
281 format!(
282 "\"path\": \"{}\", \"at\": {}, \"read\": \"{}\", \"arg\": {}, \"binds\": {}",
283 escape(path),
284 span(*at),
285 read(*address),
286 node(arg),
287 list(binds, argument)
288 ),
289 ),
290 Form::SelfRef { at, arg, address } => (
291 "self",
292 format!(
293 "\"at\": {}, \"read\": \"{}\", \"arg\": {}",
294 span(*at),
295 read(*address),
296 node(arg)
297 ),
298 ),
299 Form::Indexed { name, at, arg } => (
300 "parameter",
301 format!(
302 "\"name\": \"{}\", \"at\": {}, \"read\": \"{}\", \"arg\": {}",
303 escape(name),
304 span(*at),
305 read(Address::Index),
306 node(arg)
307 ),
308 ),
309 };
310 format!(
311 "{{ \"kind\": \"{kind}\", \"span\": {}, \"written\": {}, {fields} }}",
312 span(o.span),
313 o.written
314 )
315}
316
317fn read(address: Address) -> &'static str {
319 match address {
320 Address::Time => "time",
321 Address::Index => "index",
322 }
323}
324
325fn argument(a: &Argument) -> String {
326 match a {
327 Argument::Positional(value) => {
328 format!("{{ \"kind\": \"positional\", \"value\": {} }}", node(value))
329 }
330 Argument::Named { name, at, value } => format!(
331 "{{ \"kind\": \"named\", \"name\": \"{}\", \"at\": {}, \"value\": {} }}",
332 escape(name),
333 span(*at),
334 node(value)
335 ),
336 }
337}
338
339fn literal(l: &Literal) -> String {
342 let (unit, value) = match l {
343 Literal::Num(v) => ("number", num(*v)),
344 Literal::Bars(v) => ("bars", num(*v)),
345 Literal::Samples(v) => ("samples", num(*v)),
346 Literal::Str(s) => ("text", format!("\"{}\"", escape(s))),
347 Literal::Path(path) => ("path", format!("\"{}\"", escape(path))),
348 Literal::Recorded(recording) => ("path", format!("\"{}\"", escape(&recording.path))),
349 };
350 format!("\"unit\": \"{unit}\", \"value\": {value}")
351}
352
353fn symbol(op: BinOp) -> &'static str {
354 match op {
355 BinOp::Add => "+",
356 BinOp::Sub => "-",
357 BinOp::Mul => "*",
358 BinOp::Div => "/",
359 BinOp::Mod => "%",
360 }
361}