1use {super::*, CompileErrorKind::*};
2
3pub struct RecipeResolver<'src: 'run, 'run> {
4 unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
5 resolved_recipes: Table<'src, Rc<Recipe<'src>>>,
6 assignments: &'run Table<'src, Assignment<'src>>,
7}
8
9impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
10 pub fn resolve_recipes(
11 assignments: &'run Table<'src, Assignment<'src>>,
12 settings: &Settings,
13 unresolved_recipes: Table<'src, UnresolvedRecipe<'src>>,
14 ) -> CompileResult<'src, Table<'src, Rc<Recipe<'src>>>> {
15 let mut resolver = Self {
16 resolved_recipes: Table::new(),
17 unresolved_recipes,
18 assignments,
19 };
20
21 while let Some(unresolved) = resolver.unresolved_recipes.pop() {
22 resolver.resolve_recipe(&mut Vec::new(), unresolved)?;
23 }
24
25 for recipe in resolver.resolved_recipes.values() {
26 for (i, parameter) in recipe.parameters.iter().enumerate() {
27 if let Some(expression) = ¶meter.default {
28 for variable in expression.variables() {
29 resolver.resolve_variable(&variable, &recipe.parameters[..i])?;
30 }
31 }
32 }
33
34 for dependency in &recipe.dependencies {
35 for argument in &dependency.arguments {
36 for variable in argument.variables() {
37 resolver.resolve_variable(&variable, &recipe.parameters)?;
38 }
39 }
40 }
41
42 for line in &recipe.body {
43 if line.is_comment() && settings.ignore_comments {
44 continue;
45 }
46
47 for fragment in &line.fragments {
48 if let Fragment::Interpolation { expression, .. } = fragment {
49 for variable in expression.variables() {
50 resolver.resolve_variable(&variable, &recipe.parameters)?;
51 }
52 }
53 }
54 }
55 }
56
57 Ok(resolver.resolved_recipes)
58 }
59
60 fn resolve_variable(
61 &self,
62 variable: &Token<'src>,
63 parameters: &[Parameter],
64 ) -> CompileResult<'src> {
65 let name = variable.lexeme();
66
67 let defined = self.assignments.contains_key(name)
68 || parameters.iter().any(|p| p.name.lexeme() == name)
69 || constants().contains_key(name);
70
71 if !defined {
72 return Err(variable.error(UndefinedVariable { variable: name }));
73 }
74
75 Ok(())
76 }
77
78 fn resolve_recipe(
79 &mut self,
80 stack: &mut Vec<&'src str>,
81 recipe: UnresolvedRecipe<'src>,
82 ) -> CompileResult<'src, Rc<Recipe<'src>>> {
83 if let Some(resolved) = self.resolved_recipes.get(recipe.name()) {
84 return Ok(Rc::clone(resolved));
85 }
86
87 stack.push(recipe.name());
88
89 let mut dependencies: Vec<Rc<Recipe>> = Vec::new();
90 for dependency in &recipe.dependencies {
91 let name = dependency.recipe.lexeme();
92
93 if let Some(resolved) = self.resolved_recipes.get(name) {
94 dependencies.push(Rc::clone(resolved));
96 } else if stack.contains(&name) {
97 let first = stack[0];
98 stack.push(first);
99 return Err(
100 dependency.recipe.error(CircularRecipeDependency {
101 recipe: recipe.name(),
102 circle: stack
103 .iter()
104 .skip_while(|name| **name != dependency.recipe.lexeme())
105 .copied()
106 .collect(),
107 }),
108 );
109 } else if let Some(unresolved) = self.unresolved_recipes.remove(name) {
110 dependencies.push(self.resolve_recipe(stack, unresolved)?);
112 } else {
113 return Err(dependency.recipe.error(UnknownDependency {
115 recipe: recipe.name(),
116 unknown: name,
117 }));
118 }
119 }
120
121 stack.pop();
122
123 let resolved = Rc::new(recipe.resolve(dependencies)?);
124 self.resolved_recipes.insert(Rc::clone(&resolved));
125 Ok(resolved)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 analysis_error! {
134 name: circular_recipe_dependency,
135 input: "a: b\nb: a",
136 offset: 8,
137 line: 1,
138 column: 3,
139 width: 1,
140 kind: CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]},
141 }
142
143 analysis_error! {
144 name: self_recipe_dependency,
145 input: "a: a",
146 offset: 3,
147 line: 0,
148 column: 3,
149 width: 1,
150 kind: CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]},
151 }
152
153 analysis_error! {
154 name: unknown_dependency,
155 input: "a: b",
156 offset: 3,
157 line: 0,
158 column: 3,
159 width: 1,
160 kind: UnknownDependency{recipe: "a", unknown: "b"},
161 }
162
163 analysis_error! {
164 name: unknown_interpolation_variable,
165 input: "x:\n {{ hello}}",
166 offset: 9,
167 line: 1,
168 column: 6,
169 width: 5,
170 kind: UndefinedVariable{variable: "hello"},
171 }
172
173 analysis_error! {
174 name: unknown_second_interpolation_variable,
175 input: "wtf:=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}",
176 offset: 34,
177 line: 3,
178 column: 16,
179 width: 3,
180 kind: UndefinedVariable{variable: "lol"},
181 }
182
183 analysis_error! {
184 name: unknown_variable_in_default,
185 input: "a f=foo:",
186 offset: 4,
187 line: 0,
188 column: 4,
189 width: 3,
190 kind: UndefinedVariable{variable: "foo"},
191 }
192
193 analysis_error! {
194 name: unknown_variable_in_dependency_argument,
195 input: "bar x:\nfoo: (bar baz)",
196 offset: 17,
197 line: 1,
198 column: 10,
199 width: 3,
200 kind: UndefinedVariable{variable: "baz"},
201 }
202}