Skip to main content

sva_ast/
parameters.rs

1// Concern: names each parameter a node reads that neither its default lines nor its caller bind | Non-concern: the values bound, which names the language owns | IO: (body, defaults, given) -> names
2
3use crate::expr::{Arg, DEGREE, Expr, INDEX, SERIES};
4use crate::graph::{Graph, resolve_ref_path};
5use crate::vocabulary::{is_builtin, is_language_value};
6
7/// In first-read order, the body before the default lines a caller left out, each line seeing
8/// `given` and the lines above it. `given` names what the caller binds; `is_node` says whether
9/// a bareword call to an unbound name invokes a node.
10pub fn free_parameters(
11    body: &Expr,
12    defaults: &[(String, Expr)],
13    given: &[&str],
14    is_node: impl Fn(&str) -> bool,
15) -> Vec<String> {
16    let mut bound: Vec<&str> = given.to_vec();
17    bound.extend(defaults.iter().map(|(name, _)| name.as_str()));
18    let mut walk = Walk {
19        bound: bound.clone(),
20        indices: Vec::new(),
21        is_node: &is_node,
22        found: Vec::new(),
23    };
24    walk.expr(body);
25    for (at, (name, value)) in defaults.iter().enumerate() {
26        if given.contains(&name.as_str()) {
27            continue;
28        }
29        walk.bound = bound[..given.len() + at].to_vec();
30        walk.expr(value);
31    }
32    walk.found
33}
34
35impl Graph {
36    /// [`free_parameters`] of the node at `path`, a bareword call naming a node this graph
37    /// defines.
38    pub fn free_parameters(&self, path: &str) -> Vec<String> {
39        let Some(body) = self.expr(path) else {
40            return Vec::new();
41        };
42        let is_node = |name: &str| resolve_ref_path(path, name).is_some_and(|n| self.defines(&n));
43        free_parameters(body, self.defaults(path), &[], is_node)
44    }
45}
46
47struct Walk<'a, F: Fn(&str) -> bool> {
48    bound: Vec<&'a str>,
49    indices: Vec<&'a str>,
50    is_node: &'a F,
51    found: Vec<String>,
52}
53
54impl<'a, F: Fn(&str) -> bool> Walk<'a, F> {
55    fn free(&mut self, name: &str) {
56        if !self.found.iter().any(|held| held == name) {
57            self.found.push(name.to_string());
58        }
59    }
60
61    fn args(&mut self, args: &'a [Arg]) {
62        for a in args {
63            let (Arg::Pos(x) | Arg::Named(_, x)) = a;
64            self.expr(x);
65        }
66    }
67
68    fn expr(&mut self, e: &'a Expr) {
69        match e {
70            Expr::Lit(_) => {}
71            Expr::Var(name) => {
72                let held =
73                    self.bound.contains(&name.as_str()) || self.indices.contains(&name.as_str());
74                if !held && !is_language_value(name) {
75                    self.free(name);
76                }
77            }
78            Expr::Bin(_, l, r) => {
79                self.expr(l);
80                self.expr(r);
81            }
82            Expr::SelfRef { arg, .. } => self.expr(arg),
83            Expr::Indexed { name, arg, .. } => {
84                if !self.bound.contains(&name.as_str()) {
85                    self.free(name);
86                }
87                self.expr(arg);
88            }
89            Expr::Ref { arg, binds, .. } => {
90                self.expr(arg);
91                for (_, value) in binds {
92                    self.expr(value);
93                }
94            }
95            Expr::Call { name, args, .. }
96                if self.bound.contains(&name.as_str()) || name == DEGREE =>
97            {
98                self.args(args)
99            }
100            Expr::Call { name, args, .. } if name == SERIES => match args.as_slice() {
101                [
102                    Arg::Pos(Expr::Var(index)),
103                    Arg::Pos(lo),
104                    Arg::Pos(hi),
105                    Arg::Pos(term),
106                ] => {
107                    self.expr(lo);
108                    self.expr(hi);
109                    self.indices.push(index);
110                    self.expr(term);
111                    self.indices.pop();
112                }
113                _ => self.args(args),
114            },
115            Expr::Call { name, args, .. } if name == INDEX => match args.first() {
116                Some(Arg::Pos(time)) => self.expr(time),
117                _ => self.args(args),
118            },
119            Expr::Call { name, args, .. } if name == "concat" || name == "repeat" => {
120                self.args(args)
121            }
122            Expr::Call { name, args, .. } => {
123                self.args(args);
124                if !is_builtin(name) && !(self.is_node)(name) {
125                    self.free(name);
126                }
127            }
128        }
129    }
130}