Add assignment resolving
This commit is contained in:
parent
362158d1da
commit
810365f22b
12
notes
12
notes
@ -7,6 +7,15 @@ notes
|
|||||||
and a evaluate recipe phase that runs when a recipe is run
|
and a evaluate recipe phase that runs when a recipe is run
|
||||||
and produces the evaluated lines
|
and produces the evaluated lines
|
||||||
|
|
||||||
|
- --debug tests that:
|
||||||
|
- should consider renaming it to --evaluate if it actually evaluates stuff
|
||||||
|
- test values of assignments
|
||||||
|
- test values of interpolations
|
||||||
|
|
||||||
|
- remove evaluated_lines field from recipe
|
||||||
|
|
||||||
|
- change unknown variable to undefined variable
|
||||||
|
|
||||||
- save result of commands in variables
|
- save result of commands in variables
|
||||||
. backticks: `echo hello`
|
. backticks: `echo hello`
|
||||||
. backticks in assignments are evaluated before the first recipe is run
|
. backticks in assignments are evaluated before the first recipe is run
|
||||||
@ -20,6 +29,8 @@ notes
|
|||||||
. eval shebang recipes all at once, but plain recipes line by line
|
. eval shebang recipes all at once, but plain recipes line by line
|
||||||
. we want to avoid executing backticks before we need them
|
. we want to avoid executing backticks before we need them
|
||||||
|
|
||||||
|
- --debug mode will evaluate everything and print values after assignments and interpolation expressions
|
||||||
|
|
||||||
- nicely convert a map to option string to a map to string
|
- nicely convert a map to option string to a map to string
|
||||||
|
|
||||||
- set variables from the command line:
|
- set variables from the command line:
|
||||||
@ -54,6 +65,7 @@ notes
|
|||||||
easier to accept a program that you once rejected than to
|
easier to accept a program that you once rejected than to
|
||||||
no longer accept a program or change its meaning
|
no longer accept a program or change its meaning
|
||||||
. habit of using clever commands and writing little scripts
|
. habit of using clever commands and writing little scripts
|
||||||
|
. debugging with --debug or --evaluate
|
||||||
. very low friction to write a script (no new file, chmod, add to rcs)
|
. very low friction to write a script (no new file, chmod, add to rcs)
|
||||||
. make list of contributors, include travis
|
. make list of contributors, include travis
|
||||||
. alias .j='just --justfile ~/.justfile --working-directory ~'
|
. alias .j='just --justfile ~/.justfile --working-directory ~'
|
||||||
|
161
src/lib.rs
161
src/lib.rs
@ -284,18 +284,56 @@ impl<'a> Display for Recipe<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve<'a>(recipes: &BTreeMap<&'a str, Recipe<'a>>) -> Result<(), Error<'a>> {
|
fn resolve_recipes<'a>(
|
||||||
|
recipes: &BTreeMap<&'a str, Recipe<'a>>,
|
||||||
|
assignments: &BTreeMap<&'a str, Expression<'a>>,
|
||||||
|
text: &'a str,
|
||||||
|
) -> Result<(), Error<'a>> {
|
||||||
let mut resolver = Resolver {
|
let mut resolver = Resolver {
|
||||||
seen: HashSet::new(),
|
seen: HashSet::new(),
|
||||||
stack: vec![],
|
stack: vec![],
|
||||||
resolved: HashSet::new(),
|
resolved: HashSet::new(),
|
||||||
recipes: recipes,
|
recipes: recipes,
|
||||||
};
|
};
|
||||||
|
|
||||||
for recipe in recipes.values() {
|
for recipe in recipes.values() {
|
||||||
try!(resolver.resolve(&recipe));
|
try!(resolver.resolve(&recipe));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for recipe in recipes.values() {
|
||||||
|
for line in &recipe.lines {
|
||||||
|
for fragment in line {
|
||||||
|
if let Fragment::Expression{ref expression, ..} = *fragment {
|
||||||
|
for variable in expression.variables() {
|
||||||
|
let name = variable.lexeme;
|
||||||
|
if !(assignments.contains_key(name) || recipe.arguments.contains(&name)) {
|
||||||
|
// There's a borrow issue here that seems too difficult to solve.
|
||||||
|
// The error 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
|
||||||
|
let error = variable.error(ErrorKind::UnknownVariable{variable: name});
|
||||||
|
return Err(Error {
|
||||||
|
text: text,
|
||||||
|
index: error.index,
|
||||||
|
line: error.line,
|
||||||
|
column: error.column,
|
||||||
|
width: error.width,
|
||||||
|
kind: ErrorKind::UnknownVariable {
|
||||||
|
variable: &text[error.index..error.index + error.width.unwrap()],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +341,7 @@ struct Resolver<'a: 'b, 'b> {
|
|||||||
stack: Vec<&'a str>,
|
stack: Vec<&'a str>,
|
||||||
seen: HashSet<&'a str>,
|
seen: HashSet<&'a str>,
|
||||||
resolved: HashSet<&'a str>,
|
resolved: HashSet<&'a str>,
|
||||||
recipes: &'b BTreeMap<&'a str, Recipe<'a>>
|
recipes: &'b BTreeMap<&'a str, Recipe<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Resolver<'a, 'b> {
|
impl<'a, 'b> Resolver<'a, 'b> {
|
||||||
@ -340,6 +378,81 @@ impl<'a, 'b> Resolver<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_assignments<'a>(
|
||||||
|
assignments: &BTreeMap<&'a str, Expression<'a>>,
|
||||||
|
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
||||||
|
) -> Result<(), Error<'a>> {
|
||||||
|
|
||||||
|
let mut resolver = AssignmentResolver {
|
||||||
|
assignments: assignments,
|
||||||
|
assignment_tokens: assignment_tokens,
|
||||||
|
stack: vec![],
|
||||||
|
seen: HashSet::new(),
|
||||||
|
evaluated: HashSet::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for name in assignments.keys() {
|
||||||
|
try!(resolver.resolve_assignment(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssignmentResolver<'a: 'b, 'b> {
|
||||||
|
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
||||||
|
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
||||||
|
stack: Vec<&'a str>,
|
||||||
|
seen: HashSet<&'a str>,
|
||||||
|
evaluated: HashSet<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||||
|
fn resolve_assignment(&mut self, name: &'a str) -> Result<(), Error<'a>> {
|
||||||
|
if self.evaluated.contains(name) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.seen.insert(name);
|
||||||
|
self.stack.push(name);
|
||||||
|
|
||||||
|
if let Some(expression) = self.assignments.get(name) {
|
||||||
|
try!(self.resolve_expression(expression));
|
||||||
|
self.evaluated.insert(name);
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_expression(&mut self, expression: &Expression<'a>) -> Result<(), Error<'a>> {
|
||||||
|
match *expression {
|
||||||
|
Expression::Variable{name, ref token} => {
|
||||||
|
if self.evaluated.contains(name) {
|
||||||
|
return Ok(());
|
||||||
|
} else if self.seen.contains(name) {
|
||||||
|
let token = self.assignment_tokens.get(name).unwrap();
|
||||||
|
self.stack.push(name);
|
||||||
|
return Err(token.error(ErrorKind::CircularVariableDependency {
|
||||||
|
variable: name,
|
||||||
|
circle: self.stack.clone(),
|
||||||
|
}));
|
||||||
|
} else if self.assignments.contains_key(name) {
|
||||||
|
try!(self.resolve_assignment(name));
|
||||||
|
} else {
|
||||||
|
return Err(token.error(ErrorKind::UnknownVariable{variable: name}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::String{..} => {}
|
||||||
|
Expression::Backtick{..} => {}
|
||||||
|
Expression::Concatination{ref lhs, ref rhs} => {
|
||||||
|
try!(self.resolve_expression(lhs));
|
||||||
|
try!(self.resolve_expression(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate<'a>(
|
fn evaluate<'a>(
|
||||||
assignments: &BTreeMap<&'a str, Expression<'a>>,
|
assignments: &BTreeMap<&'a str, Expression<'a>>,
|
||||||
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
||||||
@ -1420,7 +1533,7 @@ impl<'a> Parser<'a> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
try!(resolve(&recipes));
|
try!(resolve_recipes(&recipes, &assignments, self.text));
|
||||||
|
|
||||||
for recipe in recipes.values() {
|
for recipe in recipes.values() {
|
||||||
for argument in &recipe.argument_tokens {
|
for argument in &recipe.argument_tokens {
|
||||||
@ -1439,40 +1552,10 @@ impl<'a> Parser<'a> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for line in &recipe.lines {
|
|
||||||
for piece in line {
|
|
||||||
if let Fragment::Expression{ref expression, ..} = *piece {
|
|
||||||
for variable in expression.variables() {
|
|
||||||
let name = variable.lexeme;
|
|
||||||
if !(assignments.contains_key(&name) || recipe.arguments.contains(&name)) {
|
|
||||||
// There's a borrow issue here that seems to difficult to solve.
|
|
||||||
// The error 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
|
|
||||||
let error = variable.error(ErrorKind::UnknownVariable{variable: name});
|
|
||||||
return Err(Error {
|
|
||||||
text: self.text,
|
|
||||||
index: error.index,
|
|
||||||
line: error.line,
|
|
||||||
column: error.column,
|
|
||||||
width: error.width,
|
|
||||||
kind: ErrorKind::UnknownVariable {
|
|
||||||
variable: &self.text[error.index..error.index + error.width.unwrap()],
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try!(resolve_assignments(&assignments, &assignment_tokens));
|
||||||
|
|
||||||
let values =
|
let values =
|
||||||
try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user