1use crate::expr::{Arg, DEGREE, Expr, INDEX, SERIES};
4use crate::graph::{Graph, resolve_ref_path};
5use crate::vocabulary::{is_builtin, is_language_value};
6
7pub 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 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}