pub_just/
recipe_resolver.rs

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) = &parameter.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        // dependency already resolved
95        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        // resolve unresolved dependency
111        dependencies.push(self.resolve_recipe(stack, unresolved)?);
112      } else {
113        // dependency is unknown
114        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}