2022-06-18 21:56:31 -07:00
|
|
|
use super::*;
|
2019-12-07 03:09:21 -08:00
|
|
|
|
|
|
|
pub(crate) struct Evaluator<'src: 'run, 'run> {
|
|
|
|
assignments: Option<&'run Table<'src, Assignment<'src>>>,
|
2021-09-16 06:44:40 -07:00
|
|
|
config: &'run Config,
|
|
|
|
dotenv: &'run BTreeMap<String, String>,
|
|
|
|
scope: Scope<'src, 'run>,
|
|
|
|
settings: &'run Settings<'run>,
|
|
|
|
search: &'run Search,
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'src, 'run> Evaluator<'src, 'run> {
|
|
|
|
pub(crate) fn evaluate_assignments(
|
|
|
|
assignments: &'run Table<'src, Assignment<'src>>,
|
|
|
|
config: &'run Config,
|
|
|
|
dotenv: &'run BTreeMap<String, String>,
|
|
|
|
overrides: Scope<'src, 'run>,
|
|
|
|
settings: &'run Settings<'run>,
|
2019-12-25 06:12:06 -08:00
|
|
|
search: &'run Search,
|
2019-12-07 03:09:21 -08:00
|
|
|
) -> RunResult<'src, Scope<'src, 'run>> {
|
2023-12-28 19:16:46 -08:00
|
|
|
let mut evaluator = Self {
|
2019-12-07 03:09:21 -08:00
|
|
|
scope: overrides,
|
|
|
|
assignments: Some(assignments),
|
|
|
|
config,
|
|
|
|
dotenv,
|
|
|
|
settings,
|
2019-12-25 06:12:06 -08:00
|
|
|
search,
|
2019-12-07 03:09:21 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
for assignment in assignments.values() {
|
|
|
|
evaluator.evaluate_assignment(assignment)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(evaluator.scope)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn evaluate_assignment(&mut self, assignment: &Assignment<'src>) -> RunResult<'src, &str> {
|
|
|
|
let name = assignment.name.lexeme();
|
|
|
|
|
|
|
|
if !self.scope.bound(name) {
|
|
|
|
let value = self.evaluate_expression(&assignment.value)?;
|
|
|
|
self.scope.bind(assignment.export, assignment.name, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(self.scope.value(name).unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn evaluate_expression(
|
|
|
|
&mut self,
|
|
|
|
expression: &Expression<'src>,
|
|
|
|
) -> RunResult<'src, String> {
|
|
|
|
match expression {
|
|
|
|
Expression::Variable { name, .. } => {
|
|
|
|
let variable = name.lexeme();
|
|
|
|
if let Some(value) = self.scope.value(variable) {
|
|
|
|
Ok(value.to_owned())
|
|
|
|
} else if let Some(assignment) = self
|
|
|
|
.assignments
|
|
|
|
.and_then(|assignments| assignments.get(variable))
|
|
|
|
{
|
|
|
|
Ok(self.evaluate_assignment(assignment)?.to_owned())
|
|
|
|
} else {
|
2021-07-26 01:26:06 -07:00
|
|
|
Err(Error::Internal {
|
2022-12-15 16:53:21 -08:00
|
|
|
message: format!("attempted to evaluate undefined variable `{variable}`"),
|
2019-12-07 03:09:21 -08:00
|
|
|
})
|
|
|
|
}
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
Expression::Call { thunk } => {
|
2020-01-15 02:16:13 -08:00
|
|
|
use Thunk::*;
|
|
|
|
|
2019-12-07 03:09:21 -08:00
|
|
|
let context = FunctionContext {
|
2021-09-16 06:44:40 -07:00
|
|
|
dotenv: self.dotenv,
|
2019-12-25 06:12:06 -08:00
|
|
|
invocation_directory: &self.config.invocation_directory,
|
2021-09-16 06:44:40 -07:00
|
|
|
search: self.search,
|
2019-12-07 03:09:21 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
match thunk {
|
2021-09-16 06:44:40 -07:00
|
|
|
Nullary { name, function, .. } => {
|
2021-07-26 01:26:06 -07:00
|
|
|
function(&context).map_err(|message| Error::FunctionCall {
|
2019-12-07 03:09:21 -08:00
|
|
|
function: *name,
|
|
|
|
message,
|
2021-09-16 06:44:40 -07:00
|
|
|
})
|
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
Unary {
|
|
|
|
name,
|
|
|
|
function,
|
|
|
|
arg,
|
|
|
|
..
|
|
|
|
} => function(&context, &self.evaluate_expression(arg)?).map_err(|message| {
|
2021-07-26 01:26:06 -07:00
|
|
|
Error::FunctionCall {
|
2019-12-07 03:09:21 -08:00
|
|
|
function: *name,
|
|
|
|
message,
|
|
|
|
}
|
|
|
|
}),
|
2023-06-13 05:49:46 -07:00
|
|
|
UnaryOpt {
|
|
|
|
name,
|
|
|
|
function,
|
|
|
|
args: (a, b),
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let a = self.evaluate_expression(a)?;
|
|
|
|
let b = match b.as_ref() {
|
|
|
|
Some(b) => Some(self.evaluate_expression(b)?),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
function(&context, &a, b.as_deref()).map_err(|message| Error::FunctionCall {
|
|
|
|
function: *name,
|
|
|
|
message,
|
|
|
|
})
|
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
Binary {
|
|
|
|
name,
|
|
|
|
function,
|
|
|
|
args: [a, b],
|
|
|
|
..
|
|
|
|
} => function(
|
|
|
|
&context,
|
|
|
|
&self.evaluate_expression(a)?,
|
|
|
|
&self.evaluate_expression(b)?,
|
|
|
|
)
|
2021-07-26 01:26:06 -07:00
|
|
|
.map_err(|message| Error::FunctionCall {
|
2019-12-07 03:09:21 -08:00
|
|
|
function: *name,
|
|
|
|
message,
|
|
|
|
}),
|
2021-10-14 17:00:58 -07:00
|
|
|
BinaryPlus {
|
|
|
|
name,
|
|
|
|
function,
|
|
|
|
args: ([a, b], rest),
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let a = self.evaluate_expression(a)?;
|
|
|
|
let b = self.evaluate_expression(b)?;
|
|
|
|
|
|
|
|
let mut rest_evaluated = Vec::new();
|
|
|
|
for arg in rest {
|
|
|
|
rest_evaluated.push(self.evaluate_expression(arg)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
function(&context, &a, &b, &rest_evaluated).map_err(|message| Error::FunctionCall {
|
|
|
|
function: *name,
|
|
|
|
message,
|
|
|
|
})
|
|
|
|
}
|
2021-07-03 12:39:45 -07:00
|
|
|
Ternary {
|
|
|
|
name,
|
|
|
|
function,
|
|
|
|
args: [a, b, c],
|
|
|
|
..
|
|
|
|
} => function(
|
|
|
|
&context,
|
|
|
|
&self.evaluate_expression(a)?,
|
|
|
|
&self.evaluate_expression(b)?,
|
|
|
|
&self.evaluate_expression(c)?,
|
|
|
|
)
|
2021-07-26 01:26:06 -07:00
|
|
|
.map_err(|message| Error::FunctionCall {
|
2021-07-03 12:39:45 -07:00
|
|
|
function: *name,
|
|
|
|
message,
|
|
|
|
}),
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2021-04-05 21:28:37 -07:00
|
|
|
Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.clone()),
|
2021-09-16 06:44:40 -07:00
|
|
|
Expression::Backtick { contents, token } => {
|
2019-12-07 03:09:21 -08:00
|
|
|
if self.config.dry_run {
|
2022-12-15 16:53:21 -08:00
|
|
|
Ok(format!("`{contents}`"))
|
2019-12-07 03:09:21 -08:00
|
|
|
} else {
|
|
|
|
Ok(self.run_backtick(contents, token)?)
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
|
|
|
}
|
2022-05-28 19:07:53 -07:00
|
|
|
Expression::Concatenation { lhs, rhs } => {
|
2021-09-16 06:44:40 -07:00
|
|
|
Ok(self.evaluate_expression(lhs)? + &self.evaluate_expression(rhs)?)
|
|
|
|
}
|
2020-10-26 18:16:42 -07:00
|
|
|
Expression::Conditional {
|
2024-05-14 18:55:32 -07:00
|
|
|
condition,
|
2020-10-26 18:16:42 -07:00
|
|
|
then,
|
|
|
|
otherwise,
|
|
|
|
} => {
|
2024-05-14 18:55:32 -07:00
|
|
|
if self.evaluate_condition(condition)? {
|
2020-10-26 18:16:42 -07:00
|
|
|
self.evaluate_expression(then)
|
|
|
|
} else {
|
|
|
|
self.evaluate_expression(otherwise)
|
|
|
|
}
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
Expression::Group { contents } => self.evaluate_expression(contents),
|
2022-09-11 00:48:02 -07:00
|
|
|
Expression::Join { lhs: None, rhs } => Ok("/".to_string() + &self.evaluate_expression(rhs)?),
|
|
|
|
Expression::Join {
|
|
|
|
lhs: Some(lhs),
|
|
|
|
rhs,
|
|
|
|
} => Ok(self.evaluate_expression(lhs)? + "/" + &self.evaluate_expression(rhs)?),
|
2024-05-14 18:55:32 -07:00
|
|
|
Expression::Assert { condition, error } => {
|
|
|
|
if self.evaluate_condition(condition)? {
|
|
|
|
Ok(String::new())
|
|
|
|
} else {
|
|
|
|
Err(Error::Assert {
|
|
|
|
message: self.evaluate_expression(error)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-14 18:55:32 -07:00
|
|
|
fn evaluate_condition(&mut self, condition: &Condition<'src>) -> RunResult<'src, bool> {
|
|
|
|
let lhs_value = self.evaluate_expression(&condition.lhs)?;
|
|
|
|
let rhs_value = self.evaluate_expression(&condition.rhs)?;
|
|
|
|
let condition = match condition.operator {
|
|
|
|
ConditionalOperator::Equality => lhs_value == rhs_value,
|
|
|
|
ConditionalOperator::Inequality => lhs_value != rhs_value,
|
|
|
|
ConditionalOperator::RegexMatch => Regex::new(&rhs_value)
|
|
|
|
.map_err(|source| Error::RegexCompile { source })?
|
|
|
|
.is_match(&lhs_value),
|
|
|
|
};
|
|
|
|
Ok(condition)
|
|
|
|
}
|
|
|
|
|
2019-12-07 03:09:21 -08:00
|
|
|
fn run_backtick(&self, raw: &str, token: &Token<'src>) -> RunResult<'src, String> {
|
|
|
|
let mut cmd = self.settings.shell_command(self.config);
|
|
|
|
|
|
|
|
cmd.arg(raw);
|
|
|
|
|
2019-12-25 06:12:06 -08:00
|
|
|
cmd.current_dir(&self.search.working_directory);
|
2019-12-07 03:09:21 -08:00
|
|
|
|
2021-03-25 17:00:32 -07:00
|
|
|
cmd.export(self.settings, self.dotenv, &self.scope);
|
2019-12-07 03:09:21 -08:00
|
|
|
|
2023-10-16 20:07:09 -07:00
|
|
|
cmd.stdin(Stdio::inherit());
|
2019-12-07 03:09:21 -08:00
|
|
|
|
2020-10-01 20:00:15 -07:00
|
|
|
cmd.stderr(if self.config.verbosity.quiet() {
|
2023-10-16 20:07:09 -07:00
|
|
|
Stdio::null()
|
2019-12-07 03:09:21 -08:00
|
|
|
} else {
|
2023-10-16 20:07:09 -07:00
|
|
|
Stdio::inherit()
|
2019-12-07 03:09:21 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
InterruptHandler::guard(|| {
|
2021-07-26 01:26:06 -07:00
|
|
|
output(cmd).map_err(|output_error| Error::Backtick {
|
2019-12-07 03:09:21 -08:00
|
|
|
token: *token,
|
|
|
|
output_error,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-08 22:37:12 -07:00
|
|
|
pub(crate) fn evaluate_line(
|
|
|
|
&mut self,
|
|
|
|
line: &Line<'src>,
|
|
|
|
continued: bool,
|
|
|
|
) -> RunResult<'src, String> {
|
2019-12-07 03:09:21 -08:00
|
|
|
let mut evaluated = String::new();
|
2020-06-08 22:37:12 -07:00
|
|
|
for (i, fragment) in line.fragments.iter().enumerate() {
|
2019-12-07 03:09:21 -08:00
|
|
|
match fragment {
|
2021-03-24 19:46:53 -07:00
|
|
|
Fragment::Text { token } => {
|
|
|
|
let lexeme = token.lexeme().replace("{{{{", "{{");
|
|
|
|
|
2020-06-08 22:37:12 -07:00
|
|
|
if i == 0 && continued {
|
2021-03-24 19:46:53 -07:00
|
|
|
evaluated += lexeme.trim_start();
|
2020-06-08 22:37:12 -07:00
|
|
|
} else {
|
2021-03-24 19:46:53 -07:00
|
|
|
evaluated += &lexeme;
|
|
|
|
}
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
Fragment::Interpolation { expression } => {
|
|
|
|
evaluated += &self.evaluate_expression(expression)?;
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(evaluated)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn evaluate_parameters(
|
|
|
|
config: &'run Config,
|
|
|
|
dotenv: &'run BTreeMap<String, String>,
|
|
|
|
parameters: &[Parameter<'src>],
|
2024-01-08 13:26:33 -08:00
|
|
|
arguments: &[String],
|
2019-12-07 03:09:21 -08:00
|
|
|
scope: &'run Scope<'src, 'run>,
|
|
|
|
settings: &'run Settings,
|
2019-12-25 06:12:06 -08:00
|
|
|
search: &'run Search,
|
2021-05-02 03:25:43 -07:00
|
|
|
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
|
2023-12-28 19:16:46 -08:00
|
|
|
let mut evaluator = Self {
|
2019-12-07 03:09:21 -08:00
|
|
|
assignments: None,
|
2021-03-25 17:00:32 -07:00
|
|
|
scope: scope.child(),
|
2019-12-25 06:12:06 -08:00
|
|
|
search,
|
2019-12-07 03:09:21 -08:00
|
|
|
settings,
|
|
|
|
dotenv,
|
|
|
|
config,
|
|
|
|
};
|
|
|
|
|
2021-03-25 17:00:32 -07:00
|
|
|
let mut scope = scope.child();
|
2019-12-07 03:09:21 -08:00
|
|
|
|
2021-05-02 03:25:43 -07:00
|
|
|
let mut positional = Vec::new();
|
|
|
|
|
2019-12-07 03:09:21 -08:00
|
|
|
let mut rest = arguments;
|
|
|
|
for parameter in parameters {
|
|
|
|
let value = if rest.is_empty() {
|
2020-01-15 02:16:13 -08:00
|
|
|
if let Some(ref default) = parameter.default {
|
2021-05-02 03:25:43 -07:00
|
|
|
let value = evaluator.evaluate_expression(default)?;
|
|
|
|
positional.push(value.clone());
|
|
|
|
value
|
2020-06-13 01:49:13 -07:00
|
|
|
} else if parameter.kind == ParameterKind::Star {
|
|
|
|
String::new()
|
2020-01-15 02:16:13 -08:00
|
|
|
} else {
|
2021-07-26 01:26:06 -07:00
|
|
|
return Err(Error::Internal {
|
2021-02-15 01:18:31 -08:00
|
|
|
message: "missing parameter without default".to_owned(),
|
2020-01-15 02:16:13 -08:00
|
|
|
});
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
2020-06-13 01:49:13 -07:00
|
|
|
} else if parameter.kind.is_variadic() {
|
2021-05-02 03:25:43 -07:00
|
|
|
for value in rest {
|
2024-01-08 13:26:33 -08:00
|
|
|
positional.push(value.clone());
|
2021-05-02 03:25:43 -07:00
|
|
|
}
|
2019-12-07 03:09:21 -08:00
|
|
|
let value = rest.to_vec().join(" ");
|
|
|
|
rest = &[];
|
|
|
|
value
|
|
|
|
} else {
|
2024-01-08 13:26:33 -08:00
|
|
|
let value = rest[0].clone();
|
2021-05-02 03:25:43 -07:00
|
|
|
positional.push(value.clone());
|
2019-12-07 03:09:21 -08:00
|
|
|
rest = &rest[1..];
|
|
|
|
value
|
|
|
|
};
|
2021-03-25 18:35:24 -07:00
|
|
|
scope.bind(parameter.export, parameter.name, value);
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
|
|
|
|
2021-05-02 03:25:43 -07:00
|
|
|
Ok((scope, positional))
|
2019-12-07 03:09:21 -08:00
|
|
|
}
|
|
|
|
|
2019-12-07 04:03:03 -08:00
|
|
|
pub(crate) fn recipe_evaluator(
|
2019-12-07 03:09:21 -08:00
|
|
|
config: &'run Config,
|
|
|
|
dotenv: &'run BTreeMap<String, String>,
|
|
|
|
scope: &'run Scope<'src, 'run>,
|
|
|
|
settings: &'run Settings,
|
2019-12-25 06:12:06 -08:00
|
|
|
search: &'run Search,
|
2019-12-07 03:09:21 -08:00
|
|
|
) -> Evaluator<'src, 'run> {
|
2023-12-28 19:16:46 -08:00
|
|
|
Self {
|
2019-12-07 03:09:21 -08:00
|
|
|
assignments: None,
|
|
|
|
scope: Scope::child(scope),
|
2019-12-25 06:12:06 -08:00
|
|
|
search,
|
2019-12-07 03:09:21 -08:00
|
|
|
settings,
|
|
|
|
dotenv,
|
|
|
|
config,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
run_error! {
|
|
|
|
name: backtick_code,
|
|
|
|
src: "
|
|
|
|
a:
|
|
|
|
echo {{`f() { return 100; }; f`}}
|
|
|
|
",
|
|
|
|
args: ["a"],
|
2021-07-26 01:26:06 -07:00
|
|
|
error: Error::Backtick {
|
2019-12-07 03:09:21 -08:00
|
|
|
token,
|
|
|
|
output_error: OutputError::Code(code),
|
|
|
|
},
|
|
|
|
check: {
|
|
|
|
assert_eq!(code, 100);
|
|
|
|
assert_eq!(token.lexeme(), "`f() { return 100; }; f`");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
run_error! {
|
|
|
|
name: export_assignment_backtick,
|
|
|
|
src: r#"
|
2021-03-28 23:39:23 -07:00
|
|
|
export exported_variable := "A"
|
|
|
|
b := `echo $exported_variable`
|
2019-12-07 03:09:21 -08:00
|
|
|
|
|
|
|
recipe:
|
|
|
|
echo {{b}}
|
|
|
|
"#,
|
|
|
|
args: ["--quiet", "recipe"],
|
2021-07-26 01:26:06 -07:00
|
|
|
error: Error::Backtick {
|
2019-12-07 03:09:21 -08:00
|
|
|
token,
|
|
|
|
output_error: OutputError::Code(_),
|
|
|
|
},
|
|
|
|
check: {
|
|
|
|
assert_eq!(token.lexeme(), "`echo $exported_variable`");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|