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 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 Ok(Traceable {
45 bpm: number(graph.global("bpm")),
46 meter: text(graph.global("meter")),
47 traced,
48 })
49}
50
51fn probe(graph: &mut Graph, text: &str) -> Result<Traced, CliError> {
53 define_probe_for(graph, text)?;
54 let roots = entry_points(graph);
55 sva_engine::trace(graph, &roots, PROBE).map_err(CliError::Engine)
56}
57
58fn number(e: Option<&Expr>) -> Option<f64> {
59 match e {
60 Some(Expr::Lit(Literal::Num(v))) => Some(*v),
61 _ => None,
62 }
63}
64
65fn text(e: Option<&Expr>) -> Option<String> {
66 match e {
67 Some(Expr::Lit(Literal::Str(v))) => Some(v.clone()),
68 _ => None,
69 }
70}
71
72fn referenced(graph: &Graph) -> BTreeSet<String> {
73 let mut reached: BTreeSet<String> = BTreeSet::new();
74 for path in graph.paths() {
75 if let Some(expr) = graph.expr(path) {
76 collect_refs(path, expr, &mut reached);
77 }
78 }
79 reached
80}
81
82pub(crate) fn entry_points(graph: &Graph) -> Vec<String> {
84 let reached = referenced(graph);
85 graph
86 .paths()
87 .filter(|p| !reached.contains(*p))
88 .filter(|p| !sva_core::RESERVED_VARIABLES.contains(&p.rsplit('/').next().unwrap_or(p)))
89 .filter(|p| !sva_engine::instantiate::has_free_parameter(graph, p))
90 .map(str::to_string)
91 .collect()
92}
93
94fn collect_refs(from: &str, expr: &Expr, out: &mut BTreeSet<String>) {
95 if let Expr::Ref { path, .. } = expr
96 && let Some(resolved) = resolve_ref_path(from, path)
97 {
98 out.insert(resolved);
99 }
100 for child in children(expr, Binds::Substitute) {
101 collect_refs(from, child, out);
102 }
103}