2019-04-11 15:23:14 -07:00
|
|
|
use crate::common::*;
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-17 17:28:06 -08:00
|
|
|
use CompilationErrorKind::*;
|
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
pub struct RecipeResolver<'a: 'b, 'b> {
|
2018-12-08 14:29:41 -08:00
|
|
|
stack: Vec<&'a str>,
|
2019-04-11 15:23:14 -07:00
|
|
|
seen: BTreeSet<&'a str>,
|
|
|
|
resolved: BTreeSet<&'a str>,
|
|
|
|
recipes: &'b BTreeMap<&'a str, Recipe<'a>>,
|
2017-11-18 01:18:04 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
impl<'a, 'b> RecipeResolver<'a, 'b> {
|
|
|
|
pub fn resolve_recipes(
|
2019-04-11 15:23:14 -07:00
|
|
|
recipes: &BTreeMap<&'a str, Recipe<'a>>,
|
|
|
|
assignments: &BTreeMap<&'a str, Expression<'a>>,
|
2018-12-08 14:29:41 -08:00
|
|
|
text: &'a str,
|
2017-11-18 01:18:04 -08:00
|
|
|
) -> CompilationResult<'a, ()> {
|
|
|
|
let mut resolver = RecipeResolver {
|
2018-12-08 14:29:41 -08:00
|
|
|
seen: empty(),
|
|
|
|
stack: empty(),
|
2018-03-05 13:21:35 -08:00
|
|
|
resolved: empty(),
|
|
|
|
recipes,
|
2017-11-18 01:18:04 -08:00
|
|
|
};
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
for recipe in recipes.values() {
|
|
|
|
resolver.resolve_recipe(recipe)?;
|
|
|
|
resolver.seen = empty();
|
|
|
|
}
|
|
|
|
|
2017-12-02 05:37:10 -08:00
|
|
|
// There are borrow issues here that seems too difficult to solve.
|
|
|
|
// The errors derived from the variable token has too short a lifetime,
|
|
|
|
// so we create a new error from its contents, which do live long
|
|
|
|
// enough.
|
|
|
|
//
|
|
|
|
// I suspect the solution here is to give recipes, pieces, and expressions
|
|
|
|
// two lifetime parameters instead of one, with one being the lifetime
|
|
|
|
// of the struct, and the second being the lifetime of the tokens
|
|
|
|
// that it contains.
|
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
for recipe in recipes.values() {
|
|
|
|
for line in &recipe.lines {
|
|
|
|
for fragment in line {
|
2018-12-08 14:29:41 -08:00
|
|
|
if let Fragment::Expression { ref expression, .. } = *fragment {
|
2017-12-02 14:59:07 -08:00
|
|
|
for (function, argc) in expression.functions() {
|
2018-03-17 09:17:41 -07:00
|
|
|
if let Err(error) = resolve_function(function, argc) {
|
2017-12-02 05:37:10 -08:00
|
|
|
return Err(CompilationError {
|
2018-12-08 14:29:41 -08:00
|
|
|
index: error.index,
|
|
|
|
line: error.line,
|
2017-12-02 05:37:10 -08:00
|
|
|
column: error.column,
|
2018-12-08 14:29:41 -08:00
|
|
|
width: error.width,
|
|
|
|
kind: UnknownFunction {
|
2017-12-02 05:37:10 -08:00
|
|
|
function: &text[error.index..error.index + error.width.unwrap()],
|
2018-03-05 13:21:35 -08:00
|
|
|
},
|
|
|
|
text,
|
2017-12-02 05:37:10 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2017-11-18 01:18:04 -08:00
|
|
|
for variable in expression.variables() {
|
|
|
|
let name = variable.lexeme;
|
|
|
|
let undefined = !assignments.contains_key(name)
|
|
|
|
&& !recipe.parameters.iter().any(|p| p.name == name);
|
|
|
|
if undefined {
|
2018-12-08 14:29:41 -08:00
|
|
|
let error = variable.error(UndefinedVariable { variable: name });
|
2017-11-18 01:18:04 -08:00
|
|
|
return Err(CompilationError {
|
2018-12-08 14:29:41 -08:00
|
|
|
index: error.index,
|
|
|
|
line: error.line,
|
2017-11-18 01:18:04 -08:00
|
|
|
column: error.column,
|
2018-12-08 14:29:41 -08:00
|
|
|
width: error.width,
|
|
|
|
kind: UndefinedVariable {
|
2017-11-18 01:18:04 -08:00
|
|
|
variable: &text[error.index..error.index + error.width.unwrap()],
|
2018-03-05 13:21:35 -08:00
|
|
|
},
|
|
|
|
text,
|
2017-11-18 01:18:04 -08:00
|
|
|
});
|
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:18:04 -08:00
|
|
|
fn resolve_recipe(&mut self, recipe: &Recipe<'a>) -> CompilationResult<'a, ()> {
|
2017-11-16 23:30:08 -08:00
|
|
|
if self.resolved.contains(recipe.name) {
|
2018-12-08 14:29:41 -08:00
|
|
|
return Ok(());
|
2017-11-16 23:30:08 -08:00
|
|
|
}
|
|
|
|
self.stack.push(recipe.name);
|
|
|
|
self.seen.insert(recipe.name);
|
|
|
|
for dependency_token in &recipe.dependency_tokens {
|
|
|
|
match self.recipes.get(dependency_token.lexeme) {
|
2018-12-08 14:29:41 -08:00
|
|
|
Some(dependency) => {
|
|
|
|
if !self.resolved.contains(dependency.name) {
|
|
|
|
if self.seen.contains(dependency.name) {
|
|
|
|
let first = self.stack[0];
|
|
|
|
self.stack.push(first);
|
|
|
|
return Err(
|
|
|
|
dependency_token.error(CircularRecipeDependency {
|
|
|
|
recipe: recipe.name,
|
|
|
|
circle: self
|
|
|
|
.stack
|
|
|
|
.iter()
|
|
|
|
.skip_while(|name| **name != dependency.name)
|
|
|
|
.cloned()
|
|
|
|
.collect(),
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
self.resolve_recipe(dependency)?;
|
2017-11-16 23:30:08 -08:00
|
|
|
}
|
2018-12-08 14:29:41 -08:00
|
|
|
}
|
|
|
|
None => {
|
|
|
|
return Err(dependency_token.error(UnknownDependency {
|
|
|
|
recipe: recipe.name,
|
|
|
|
unknown: dependency_token.lexeme,
|
2019-04-11 12:30:29 -07:00
|
|
|
}));
|
2018-12-08 14:29:41 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
self.resolved.insert(recipe.name);
|
|
|
|
self.stack.pop();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
2017-11-18 01:44:59 -08:00
|
|
|
compilation_error_test! {
|
|
|
|
name: circular_recipe_dependency,
|
|
|
|
input: "a: b\nb: a",
|
|
|
|
index: 8,
|
|
|
|
line: 1,
|
|
|
|
column: 3,
|
|
|
|
width: Some(1),
|
|
|
|
kind: CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]},
|
2017-11-17 17:28:06 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:44:59 -08:00
|
|
|
compilation_error_test! {
|
|
|
|
name: self_recipe_dependency,
|
|
|
|
input: "a: a",
|
|
|
|
index: 3,
|
|
|
|
line: 0,
|
|
|
|
column: 3,
|
|
|
|
width: Some(1),
|
|
|
|
kind: CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]},
|
2017-11-17 17:28:06 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:44:59 -08:00
|
|
|
compilation_error_test! {
|
|
|
|
name: unknown_dependency,
|
|
|
|
input: "a: b",
|
|
|
|
index: 3,
|
|
|
|
line: 0,
|
|
|
|
column: 3,
|
|
|
|
width: Some(1),
|
|
|
|
kind: UnknownDependency{recipe: "a", unknown: "b"},
|
2017-11-17 17:28:06 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:44:59 -08:00
|
|
|
compilation_error_test! {
|
|
|
|
name: unknown_interpolation_variable,
|
|
|
|
input: "x:\n {{ hello}}",
|
|
|
|
index: 9,
|
|
|
|
line: 1,
|
|
|
|
column: 6,
|
|
|
|
width: Some(5),
|
|
|
|
kind: UndefinedVariable{variable: "hello"},
|
2017-11-17 17:28:06 -08:00
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
|
2017-11-18 01:44:59 -08:00
|
|
|
compilation_error_test! {
|
|
|
|
name: unknown_second_interpolation_variable,
|
|
|
|
input: "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}",
|
|
|
|
index: 33,
|
|
|
|
line: 3,
|
|
|
|
column: 16,
|
|
|
|
width: Some(3),
|
|
|
|
kind: UndefinedVariable{variable: "lol"},
|
2017-11-17 17:28:06 -08:00
|
|
|
}
|
2017-12-02 05:37:10 -08:00
|
|
|
|
|
|
|
compilation_error_test! {
|
|
|
|
name: unknown_function_in_interpolation,
|
|
|
|
input: "a:\n echo {{bar()}}",
|
|
|
|
index: 11,
|
|
|
|
line: 1,
|
|
|
|
column: 8,
|
|
|
|
width: Some(3),
|
|
|
|
kind: UnknownFunction{function: "bar"},
|
|
|
|
}
|
2017-11-16 23:30:08 -08:00
|
|
|
}
|