Skip to main content

sva_cli/
trace.rs

1// Concern: seeds a trace from every entry point and names what the caller asked for | Non-concern: the traversal itself (sva-engine), JSON shape (output.rs) | IO: (dir, target) -> Traced or CliError
2
3use 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
17/// Every entry point is a root, and an uninstantiated bench refuses. A structure has no
18/// time, so an interval the target reads is dropped.
19pub 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
51/// `text` is neither node nor instance: argv math, an entry point of its own.
52fn 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
82/// Every node nothing references, bar a library node (one with a free parameter).
83pub(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}