just/src/assignment_evaluator.rs
2017-12-11 21:44:45 +01:00

191 lines
5.2 KiB
Rust

use common::*;
use brev;
pub struct AssignmentEvaluator<'a: 'b, 'b> {
pub assignments: &'b Map<&'a str, Expression<'a>>,
pub evaluated: Map<&'a str, String>,
pub exports: &'b Set<&'a str>,
pub overrides: &'b Map<&'b str, &'b str>,
pub quiet: bool,
pub scope: &'b Map<&'a str, String>,
pub shell: &'b str,
pub dry_run: bool,
}
impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
pub fn evaluate_assignments(
assignments: &Map<&'a str, Expression<'a>>,
overrides: &Map<&str, &str>,
quiet: bool,
shell: &'a str,
dry_run: bool,
) -> RunResult<'a, Map<&'a str, String>> {
let mut evaluator = AssignmentEvaluator {
assignments: assignments,
evaluated: empty(),
exports: &empty(),
overrides: overrides,
quiet: quiet,
scope: &empty(),
shell: shell,
dry_run: dry_run,
};
for name in assignments.keys() {
evaluator.evaluate_assignment(name)?;
}
Ok(evaluator.evaluated)
}
pub fn evaluate_line(
&mut self,
line: &[Fragment<'a>],
arguments: &Map<&str, Cow<str>>
) -> RunResult<'a, String> {
let mut evaluated = String::new();
for fragment in line {
match *fragment {
Fragment::Text{ref text} => evaluated += text.lexeme,
Fragment::Expression{ref expression} => {
evaluated += &self.evaluate_expression(expression, arguments)?;
}
}
}
Ok(evaluated)
}
fn evaluate_assignment(&mut self, name: &'a str) -> RunResult<'a, ()> {
if self.evaluated.contains_key(name) {
return Ok(());
}
if let Some(expression) = self.assignments.get(name) {
if let Some(value) = self.overrides.get(name) {
self.evaluated.insert(name, value.to_string());
} else {
let value = self.evaluate_expression(expression, &empty())?;
self.evaluated.insert(name, value);
}
} else {
return Err(RuntimeError::Internal {
message: format!("attempted to evaluated unknown assignment {}", name)
});
}
Ok(())
}
fn evaluate_expression(
&mut self,
expression: &Expression<'a>,
arguments: &Map<&str, Cow<str>>
) -> RunResult<'a, String> {
match *expression {
Expression::Variable{name, ..} => {
if self.evaluated.contains_key(name) {
Ok(self.evaluated[name].clone())
} else if self.scope.contains_key(name) {
Ok(self.scope[name].clone())
} else if self.assignments.contains_key(name) {
self.evaluate_assignment(name)?;
Ok(self.evaluated[name].clone())
} else if arguments.contains_key(name) {
Ok(arguments[name].to_string())
} else {
Err(RuntimeError::Internal {
message: format!("attempted to evaluate undefined variable `{}`", name)
})
}
}
Expression::Call{name, arguments: ref call_arguments, ref token} => {
let call_arguments = call_arguments.iter().map(|argument| {
self.evaluate_expression(argument, arguments)
}).collect::<Result<Vec<String>, RuntimeError>>()?;
::functions::evaluate_function(token, name, &call_arguments)
}
Expression::String{ref cooked_string} => Ok(cooked_string.cooked.clone()),
Expression::Backtick{raw, ref token} => {
if self.dry_run {
Ok(format!("`{}`", raw))
} else {
Ok(self.run_backtick(raw, token)?)
}
}
Expression::Concatination{ref lhs, ref rhs} => {
Ok(
self.evaluate_expression(lhs, arguments)?
+
&self.evaluate_expression(rhs, arguments)?
)
}
}
}
fn run_backtick(
&self,
raw: &str,
token: &Token<'a>,
) -> RunResult<'a, String> {
let mut cmd = Command::new(self.shell);
cmd.export_environment_variables(self.scope, self.exports)?;
cmd.arg("-cu")
.arg(raw);
cmd.stderr(if self.quiet {
process::Stdio::null()
} else {
process::Stdio::inherit()
});
brev::output(cmd)
.map_err(|output_error| RuntimeError::Backtick{token: token.clone(), output_error})
}
}
#[cfg(test)]
mod test {
use super::*;
use brev::OutputError;
use testing::parse_success;
use Configuration;
#[test]
fn backtick_code() {
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
.run(&["a"], &Default::default()).unwrap_err() {
RuntimeError::Backtick{token, output_error: OutputError::Code(code)} => {
assert_eq!(code, 100);
assert_eq!(token.lexeme, "`f() { return 100; }; f`");
},
other => panic!("expected a code run error, but got: {}", other),
}
}
#[test]
fn export_assignment_backtick() {
let text = r#"
export exported_variable = "A"
b = `echo $exported_variable`
recipe:
echo {{b}}
"#;
let configuration = Configuration {
quiet: true,
..Default::default()
};
match parse_success(text).run(&["recipe"], &configuration).unwrap_err() {
RuntimeError::Backtick{token, output_error: OutputError::Code(_)} => {
assert_eq!(token.lexeme, "`echo $exported_variable`");
},
other => panic!("expected a backtick code errror, but got: {}", other),
}
}
}