use super::{Ast, FunctionName, Node, VariableName};
use crate::Template;
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct AstReferences {
pub(crate) functions: Vec<FunctionName>,
pub(crate) variables: Vec<VariableName>,
}
impl AstReferences {
pub fn is_empty(&self) -> bool {
self.functions.is_empty() && self.variables.is_empty()
}
}
impl Node {
pub(crate) fn extract_references(&self, template: &Template) -> AstReferences {
let mut fns = vec![];
let mut vars = vec![];
extract_dfs(&Box::new(self.clone()), template, &mut fns, &mut vars);
AstReferences {
functions: fns,
variables: vars,
}
}
}
fn extract_dfs(
ast: &Ast,
template: &Template,
fns: &mut Vec<FunctionName>,
vars: &mut Vec<VariableName>,
) {
match &**ast {
Node::FunctionCall { name, args } => {
if template.is_function_defined(name) {
fns.push(name.to_string());
}
for arg in args {
extract_dfs(arg, template, fns, vars);
}
}
Node::InfixFunctionCall { left, right, .. } => {
extract_dfs(left, template, fns, vars);
extract_dfs(right, template, fns, vars);
}
Node::Reference(r) if template.is_variable_defined(r) => vars.push(r.to_string()),
_ => (),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::BuiltinFunction;
use crate::test_utils::*;
use crate::{Runtime, Spreadsheet};
fn build_template(runtime: &Runtime) -> Template {
Template::new(Spreadsheet::default(), None, runtime)
}
#[test]
fn extract_references_empty() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let references = Node::extract_references(&Box::new(5.into()), &build_template(&runtime));
assert!(references.is_empty());
}
#[test]
fn extract_references_fns_builtin() {
let test_file = TestFile::new("csv", "");
let mut runtime: Runtime = test_file.into();
runtime.builtin_functions.insert(
"foo".to_string(),
BuiltinFunction {
eval: Box::new(|_, _| Ok(Node::reference("return value"))),
name: "foo".to_string(),
},
);
let template = build_template(&runtime);
let references = Node::extract_references(
&Box::new(Node::fn_call(
"foo",
&[Node::reference("bar"), Node::reference("baz")],
)),
&template,
);
assert_eq!(references.functions.len(), 1);
assert_eq!(&references.functions[0], "foo");
}
#[test]
fn extract_references_fns_user_defined() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template.functions.insert(
"foo".to_string(),
Box::new(Node::fn_def(
"foo",
&["a", "b"],
Node::reference("return value"),
)),
);
let references = Node::extract_references(
&Box::new(Node::fn_call(
"foo",
&[Node::reference("bar"), Node::reference("baz")],
)),
&template,
);
assert_eq!(references.functions.len(), 1);
assert_eq!(&references.functions[0], "foo");
}
#[test]
fn extract_references_fns_infix() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template.functions.insert(
"foo".to_string(),
Box::new(Node::fn_def(
"foo",
&["a", "b"],
Node::reference("return value"),
)),
);
let references = Node::extract_references(
&Box::new(Node::infix_fn_call(
Node::fn_call("foo", &[Node::reference("bar"), Node::reference("baz")]),
"+",
Node::fn_call("bar", &[Node::reference("bar"), Node::reference("baz")]),
)),
&template,
);
assert_eq!(references.functions.len(), 1);
assert_eq!(&references.functions[0], "foo");
}
#[test]
fn extract_references_fns_nested() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template.functions.insert(
"foo".to_string(),
Box::new(Node::fn_def(
"foo",
&["a", "b"],
Node::reference("return value"),
)),
);
let references = Node::extract_references(
&Box::new(Node::fn_call(
"foo_outer",
&[Node::fn_call(
"foo",
&[Node::reference("bar"), Node::reference("baz")],
)],
)),
&template,
);
assert_eq!(references.functions.len(), 1);
assert_eq!(&references.functions[0], "foo");
}
#[test]
fn extract_references_vars() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template
.variables
.insert("foo".to_string(), Box::new(Node::reference("return value")));
let references = Node::extract_references(&Box::new(Node::reference("foo")), &template);
assert_eq!(references.variables.len(), 1);
assert_eq!(&references.variables[0], "foo");
}
#[test]
fn extract_references_vars_nested() {
let test_file = TestFile::new("csv", "");
let runtime = test_file.into();
let mut template = build_template(&runtime);
template
.variables
.insert("bar".to_string(), Box::new(Node::reference("return value")));
let references = Node::extract_references(
&Box::new(Node::fn_call(
"foo_outer",
&[Node::fn_call(
"foo",
&[Node::reference("bar"), Node::reference("baz")],
)],
)),
&template,
);
assert_eq!(references.variables.len(), 1);
assert_eq!(&references.variables[0], "bar");
}
}