1use std::collections::{BTreeMap, BTreeSet};
4
5use sva_ast::Graph;
6
7use crate::error::{BindingFault, EngineError};
8use crate::instantiate::Instances;
9use crate::schedule::{self as refs};
10
11pub struct Up {
12 pub node: String,
13 pub expr: String,
14 pub ty: String,
15}
16
17pub struct Traced {
18 pub node: String,
19 pub expr: String,
20 pub ty: String,
22 pub discrete: Option<String>,
24 pub entry: Vec<String>,
25 pub file: Option<String>,
26 pub cycle: Option<Vec<String>>,
28 pub down: Vec<String>,
29 pub up: Vec<Up>,
30}
31
32pub fn trace(graph: &Graph, roots: &[String], target: &str) -> Result<Traced, EngineError> {
34 let seeded = (graph.defines(target) && !roots.iter().any(|r| r == target)).then(|| {
36 let mut held = roots.to_vec();
37 held.push(target.to_string());
38 held
39 });
40 let (inst, entries) = match &seeded {
41 None => crate::instantiate::from_roots(graph, roots)?,
42 Some(held) => match crate::instantiate::from_roots(graph, held) {
43 Ok(found) => found,
44 Err(EngineError::Binding {
46 fault: BindingFault::Unbound(..),
47 ..
48 }) => crate::instantiate::from_roots(graph, roots)?,
49 Err(other) => return Err(other),
50 },
51 };
52 let node = inst.instance_of(target)?;
53
54 let named = match entries.len() > roots.len() {
56 true => entries[..entries.len() - 1].to_vec(),
57 false => entries.clone(),
58 };
59 let scheduled = refs::schedule_from(&inst, &entries)?;
60 let typing = crate::typing::infer_all(&inst, &scheduled)?;
61 let groups = scheduled.groups.clone();
62 let order: Vec<String> = groups.concat();
63
64 let cycle = groups
65 .iter()
66 .filter(|g| scheduled.is_loop(g))
67 .find(|g| g.len() > 1 && g.contains(&node))
68 .map(|g| sorted(g.clone()));
69
70 let mut down = scheduled.deps(&node).to_vec();
71 if inst.reads_self(&node) {
72 down.push(node.clone());
73 }
74
75 Ok(Traced {
76 expr: expr_of(&inst, &node),
77 ty: spelled(&typing, &node),
78 discrete: typing
79 .id(&node)
80 .and_then(|id| sampled_leaf(&typing, id, &mut Vec::new())),
81 entry: sorted(named),
82 file: inst
83 .origin(&node)
84 .filter(|f| *f != node)
85 .map(str::to_string),
86 cycle,
87 up: readers(&inst, &typing, &scheduled, &order, &node),
88 down: sorted(down),
89 node,
90 })
91}
92
93fn sorted(mut names: Vec<String>) -> Vec<String> {
94 names.sort();
95 names.dedup();
96 names
97}
98
99fn expr_of(inst: &Instances, path: &str) -> String {
100 match inst.at(path) {
101 Some((e, cx)) => inst.render(e, cx),
102 None => String::new(),
103 }
104}
105
106fn spelled(typing: &crate::typing::Typing, path: &str) -> String {
108 match typing.id(path) {
109 Some(id) => crate::overload::describe(typing.ty(id)),
110 None => String::new(),
111 }
112}
113
114fn sampled_leaf(
116 typing: &crate::typing::Typing,
117 id: sva_formula::NodeId,
118 open: &mut Vec<sva_formula::NodeId>,
119) -> Option<String> {
120 if typing.ty(id).is_closed_form() || open.contains(&id) {
121 return None;
122 }
123 open.push(id);
124 let under = |set: Vec<sva_formula::NodeId>, open: &mut Vec<sva_formula::NodeId>| {
125 set.into_iter()
126 .find_map(|op| sampled_leaf(typing, op, open))
127 };
128 match typing.value(id) {
129 crate::typing::Value::SelfAt(_) => Some("sp, a self-reference on the grid".to_string()),
130 crate::typing::Value::Grid(_) => Some("sp, a duration on the grid".to_string()),
131 crate::typing::Value::Solver(_) => Some("a finite-difference builtin".to_string()),
132 crate::typing::Value::Cast(crate::cast::Cast::Sample, source) => {
133 Some(format!("sample({})", typing.name(*source)))
134 }
135 crate::typing::Value::Cast(cast, source) => {
136 under(vec![*source], open).or_else(|| Some(cast.name().to_string()))
137 }
138 crate::typing::Value::Read { source, at, .. } => under(vec![*source], open).or_else(|| {
139 Some(match at {
140 crate::offset::Offset::Steps(steps) => format!("a read {steps} samples back"),
141 crate::offset::Offset::Secs(secs) => format!("a read {secs} seconds back"),
142 })
143 }),
144 crate::typing::Value::Op { args, .. } => under(args.clone(), open),
145 crate::typing::Value::Filter { x, .. } => under(vec![*x], open),
146 crate::typing::Value::ClosedForm(_) => None,
147 }
148}
149
150fn readers(
151 inst: &Instances,
152 typing: &crate::typing::Typing,
153 scheduled: &refs::Order,
154 order: &[String],
155 node: &str,
156) -> Vec<Up> {
157 let mut above: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
158 for path in order {
159 for target in scheduled.deps(path) {
160 if target != path {
161 above
162 .entry(target.as_str())
163 .or_default()
164 .push(path.as_str());
165 }
166 }
167 }
168 let mut seen: BTreeSet<&str> = BTreeSet::from([node]);
169 let mut work: Vec<&str> = vec![node];
170 while let Some(at) = work.pop() {
171 for reader in above.get(at).map(Vec::as_slice).unwrap_or(&[]) {
172 if seen.insert(reader) {
173 work.push(reader);
174 }
175 }
176 }
177 seen.into_iter()
178 .filter(|p| *p != node)
179 .map(|p| Up {
180 node: p.to_string(),
181 expr: expr_of(inst, p),
182 ty: spelled(typing, p),
183 })
184 .collect()
185}