1use std::path::Path;
4
5use std::collections::BTreeSet;
6
7use sva_ast::{Binds, Expr, Graph, Literal, children, resolve_ref_path};
8use sva_core::{CliError, PROBE, define_probe_for, prepared, refuse_unresolved_bars};
9use sva_engine::{EngineError, Traced};
10
11pub struct Traceable {
12 pub bpm: Option<f64>,
13 pub meter: Option<String>,
14 pub traced: Traced,
15}
16
17pub fn trace(dir: &Path, target: &str) -> Result<Traceable, CliError> {
20 let target = match sva_core::target(target)? {
21 sva_core::Target {
22 expr,
23 interval: Some(_),
24 } => expr,
25 _ => target.to_string(),
26 };
27 let target = target.as_str();
28 let mut graph = prepared(&sva_ast::Dir::at(dir))?;
29 refuse_unresolved_bars(&graph)?;
30
31 let mut roots = entry_points(&graph);
32 if graph.defines(target)
33 && !roots.iter().any(|r| r == target)
34 && !referenced(&graph).contains(target)
35 {
36 roots.push(target.to_string());
37 }
38 let mut traced = match sva_engine::trace(&graph, &roots, target) {
39 Err(EngineError::UnknownNode(ref name)) if name == target && !graph.defines(target) => {
40 probe(&mut graph, target)?
41 }
42 other => other.map_err(CliError::Engine)?,
43 };
44 let readers = traced.up.iter_mut().map(|up| (&up.node, &mut up.expr));
45 for (node, expr) in std::iter::once((&traced.node, &mut traced.expr)).chain(readers) {
46 if let Some(score) = graph.score(node) {
47 *expr = summary(score);
48 }
49 }
50 Ok(Traceable {
51 bpm: number(graph.global("bpm")),
52 meter: text(graph.global("meter")),
53 traced,
54 })
55}
56
57fn summary(score: &sva_ast::Score) -> String {
59 let mut nodes: Vec<&str> = Vec::new();
60 for event in &score.events {
61 if !nodes.contains(&event.node.as_str()) {
62 nodes.push(&event.node);
63 }
64 }
65 let (first, last) = (&score.events[0], &score.events[score.events.len() - 1]);
66 format!(
67 "a score of {} lines, `at` {} to {}, each playing @{} shifted to its `at`",
68 score.events.len(),
69 first.at.spelled(),
70 last.at.spelled(),
71 nodes.join(", @")
72 )
73}
74
75fn probe(graph: &mut Graph, text: &str) -> Result<Traced, CliError> {
77 define_probe_for(graph, text)?;
78 let roots = entry_points(graph);
79 sva_engine::trace(graph, &roots, PROBE).map_err(CliError::Engine)
80}
81
82fn number(e: Option<&Expr>) -> Option<f64> {
83 match e {
84 Some(Expr::Lit(Literal::Num(v))) => Some(*v),
85 _ => None,
86 }
87}
88
89fn text(e: Option<&Expr>) -> Option<String> {
90 match e {
91 Some(Expr::Lit(Literal::Str(v))) => Some(v.clone()),
92 _ => None,
93 }
94}
95
96fn referenced(graph: &Graph) -> BTreeSet<String> {
97 let mut reached: BTreeSet<String> = BTreeSet::new();
98 for path in graph.paths() {
99 if let Some(expr) = graph.expr(path) {
100 collect_refs(path, expr, &mut reached);
101 }
102 }
103 reached
104}
105
106pub(crate) fn entry_points(graph: &Graph) -> Vec<String> {
109 let reached = referenced(graph);
110 graph
111 .paths()
112 .filter(|p| !reached.contains(*p))
113 .filter(|p| !reserved_variable(p) && graph.scale(p).is_none())
114 .filter(|p| graph.free_parameters(p).is_empty())
115 .map(str::to_string)
116 .collect()
117}
118
119pub(crate) fn reserved_variable(path: &str) -> bool {
120 sva_core::RESERVED_VARIABLES.contains(&path.rsplit('/').next().unwrap_or(path))
121}
122
123fn collect_refs(from: &str, expr: &Expr, out: &mut BTreeSet<String>) {
124 if let Expr::Ref { path, .. } = expr
125 && let Some(resolved) = resolve_ref_path(from, path)
126 {
127 out.insert(resolved);
128 }
129 for child in children(expr, Binds::Substitute) {
130 collect_refs(from, child, out);
131 }
132}