1use super::*;
2
3pub struct Evaluator<'src: 'run, 'run> {
4 pub assignments: Option<&'run Table<'src, Assignment<'src>>>,
5 pub context: ExecutionContext<'src, 'run>,
6 pub is_dependency: bool,
7 pub scope: Scope<'src, 'run>,
8}
9
10impl<'src, 'run> Evaluator<'src, 'run> {
11 pub fn evaluate_assignments(
12 config: &'run Config,
13 dotenv: &'run BTreeMap<String, String>,
14 module: &'run Justfile<'src>,
15 overrides: &BTreeMap<String, String>,
16 parent: &'run Scope<'src, 'run>,
17 search: &'run Search,
18 ) -> RunResult<'src, Scope<'src, 'run>>
19 where
20 'src: 'run,
21 {
22 let context = ExecutionContext {
23 config,
24 dotenv,
25 module,
26 scope: parent,
27 search,
28 };
29
30 let mut scope = context.scope.child();
31 let mut unknown_overrides = Vec::new();
32
33 for (name, value) in overrides {
34 if let Some(assignment) = module.assignments.get(name) {
35 scope.bind(Binding {
36 constant: false,
37 export: assignment.export,
38 file_depth: 0,
39 name: assignment.name,
40 private: assignment.private,
41 value: value.clone(),
42 });
43 } else {
44 unknown_overrides.push(name.clone());
45 }
46 }
47
48 if !unknown_overrides.is_empty() {
49 return Err(Error::UnknownOverrides {
50 overrides: unknown_overrides,
51 });
52 }
53
54 let mut evaluator = Self {
55 context,
56 assignments: Some(&module.assignments),
57 scope,
58 is_dependency: false,
59 };
60
61 for assignment in module.assignments.values() {
62 evaluator.evaluate_assignment(assignment)?;
63 }
64
65 Ok(evaluator.scope)
66 }
67
68 fn evaluate_assignment(&mut self, assignment: &Assignment<'src>) -> RunResult<'src, &str> {
69 let name = assignment.name.lexeme();
70
71 if !self.scope.bound(name) {
72 let value = self.evaluate_expression(&assignment.value)?;
73 self.scope.bind(Binding {
74 constant: false,
75 export: assignment.export,
76 file_depth: 0,
77 name: assignment.name,
78 private: assignment.private,
79 value,
80 });
81 }
82
83 Ok(self.scope.value(name).unwrap())
84 }
85
86 pub fn evaluate_expression(
87 &mut self,
88 expression: &Expression<'src>,
89 ) -> RunResult<'src, String> {
90 match expression {
91 Expression::And { lhs, rhs } => {
92 let lhs = self.evaluate_expression(lhs)?;
93 if lhs.is_empty() {
94 return Ok(String::new());
95 }
96 self.evaluate_expression(rhs)
97 }
98 Expression::Assert { condition, error } => {
99 if self.evaluate_condition(condition)? {
100 Ok(String::new())
101 } else {
102 Err(Error::Assert {
103 message: self.evaluate_expression(error)?,
104 })
105 }
106 }
107 Expression::Backtick { contents, token } => {
108 if self.context.config.dry_run {
109 Ok(format!("`{contents}`"))
110 } else {
111 Ok(self.run_backtick(contents, token)?)
112 }
113 }
114 Expression::Call { thunk } => {
115 use Thunk::*;
116 let result = match thunk {
117 Nullary { function, .. } => function(function::Context::new(self, thunk.name())),
118 Unary { function, arg, .. } => {
119 let arg = self.evaluate_expression(arg)?;
120 function(function::Context::new(self, thunk.name()), &arg)
121 }
122 UnaryOpt {
123 function,
124 args: (a, b),
125 ..
126 } => {
127 let a = self.evaluate_expression(a)?;
128 let b = match b.as_ref() {
129 Some(b) => Some(self.evaluate_expression(b)?),
130 None => None,
131 };
132 function(function::Context::new(self, thunk.name()), &a, b.as_deref())
133 }
134 UnaryPlus {
135 function,
136 args: (a, rest),
137 ..
138 } => {
139 let a = self.evaluate_expression(a)?;
140 let mut rest_evaluated = Vec::new();
141 for arg in rest {
142 rest_evaluated.push(self.evaluate_expression(arg)?);
143 }
144 function(
145 function::Context::new(self, thunk.name()),
146 &a,
147 &rest_evaluated,
148 )
149 }
150 Binary {
151 function,
152 args: [a, b],
153 ..
154 } => {
155 let a = self.evaluate_expression(a)?;
156 let b = self.evaluate_expression(b)?;
157 function(function::Context::new(self, thunk.name()), &a, &b)
158 }
159 BinaryPlus {
160 function,
161 args: ([a, b], rest),
162 ..
163 } => {
164 let a = self.evaluate_expression(a)?;
165 let b = self.evaluate_expression(b)?;
166 let mut rest_evaluated = Vec::new();
167 for arg in rest {
168 rest_evaluated.push(self.evaluate_expression(arg)?);
169 }
170 function(
171 function::Context::new(self, thunk.name()),
172 &a,
173 &b,
174 &rest_evaluated,
175 )
176 }
177 Ternary {
178 function,
179 args: [a, b, c],
180 ..
181 } => {
182 let a = self.evaluate_expression(a)?;
183 let b = self.evaluate_expression(b)?;
184 let c = self.evaluate_expression(c)?;
185 function(function::Context::new(self, thunk.name()), &a, &b, &c)
186 }
187 };
188 result.map_err(|message| Error::FunctionCall {
189 function: thunk.name(),
190 message,
191 })
192 }
193 Expression::Concatenation { lhs, rhs } => {
194 Ok(self.evaluate_expression(lhs)? + &self.evaluate_expression(rhs)?)
195 }
196 Expression::Conditional {
197 condition,
198 then,
199 otherwise,
200 } => {
201 if self.evaluate_condition(condition)? {
202 self.evaluate_expression(then)
203 } else {
204 self.evaluate_expression(otherwise)
205 }
206 }
207 Expression::Group { contents } => self.evaluate_expression(contents),
208 Expression::Join { lhs: None, rhs } => Ok("/".to_string() + &self.evaluate_expression(rhs)?),
209 Expression::Join {
210 lhs: Some(lhs),
211 rhs,
212 } => Ok(self.evaluate_expression(lhs)? + "/" + &self.evaluate_expression(rhs)?),
213 Expression::Or { lhs, rhs } => {
214 let lhs = self.evaluate_expression(lhs)?;
215 if !lhs.is_empty() {
216 return Ok(lhs);
217 }
218 self.evaluate_expression(rhs)
219 }
220 Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.clone()),
221 Expression::Variable { name, .. } => {
222 let variable = name.lexeme();
223 if let Some(value) = self.scope.value(variable) {
224 Ok(value.to_owned())
225 } else if let Some(assignment) = self
226 .assignments
227 .and_then(|assignments| assignments.get(variable))
228 {
229 Ok(self.evaluate_assignment(assignment)?.to_owned())
230 } else {
231 Err(Error::Internal {
232 message: format!("attempted to evaluate undefined variable `{variable}`"),
233 })
234 }
235 }
236 }
237 }
238
239 fn evaluate_condition(&mut self, condition: &Condition<'src>) -> RunResult<'src, bool> {
240 let lhs_value = self.evaluate_expression(&condition.lhs)?;
241 let rhs_value = self.evaluate_expression(&condition.rhs)?;
242 let condition = match condition.operator {
243 ConditionalOperator::Equality => lhs_value == rhs_value,
244 ConditionalOperator::Inequality => lhs_value != rhs_value,
245 ConditionalOperator::RegexMatch => Regex::new(&rhs_value)
246 .map_err(|source| Error::RegexCompile { source })?
247 .is_match(&lhs_value),
248 };
249 Ok(condition)
250 }
251
252 fn run_backtick(&self, raw: &str, token: &Token<'src>) -> RunResult<'src, String> {
253 self
254 .run_command(raw, &[])
255 .map_err(|output_error| Error::Backtick {
256 token: *token,
257 output_error,
258 })
259 }
260
261 pub fn run_command(&self, command: &str, args: &[&str]) -> Result<String, OutputError> {
262 let mut cmd = self
263 .context
264 .module
265 .settings
266 .shell_command(self.context.config);
267 cmd.arg(command);
268 cmd.args(args);
269 cmd.current_dir(self.context.working_directory());
270 cmd.export(
271 &self.context.module.settings,
272 self.context.dotenv,
273 &self.scope,
274 &self.context.module.unexports,
275 );
276 cmd.stdin(Stdio::inherit());
277 cmd.stderr(if self.context.config.verbosity.quiet() {
278 Stdio::null()
279 } else {
280 Stdio::inherit()
281 });
282 InterruptHandler::guard(|| output(cmd))
283 }
284
285 pub fn evaluate_line(
286 &mut self,
287 line: &Line<'src>,
288 continued: bool,
289 ) -> RunResult<'src, String> {
290 let mut evaluated = String::new();
291 for (i, fragment) in line.fragments.iter().enumerate() {
292 match fragment {
293 Fragment::Text { token } => {
294 let lexeme = token.lexeme().replace("{{{{", "{{");
295
296 if i == 0 && continued {
297 evaluated += lexeme.trim_start();
298 } else {
299 evaluated += &lexeme;
300 }
301 }
302 Fragment::Interpolation { expression } => {
303 evaluated += &self.evaluate_expression(expression)?;
304 }
305 }
306 }
307 Ok(evaluated)
308 }
309
310 pub fn evaluate_parameters(
311 context: &ExecutionContext<'src, 'run>,
312 is_dependency: bool,
313 arguments: &[String],
314 parameters: &[Parameter<'src>],
315 ) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
316 let mut evaluator = Self::new(context, is_dependency, context.scope);
317
318 let mut positional = Vec::new();
319
320 let mut rest = arguments;
321 for parameter in parameters {
322 let value = if rest.is_empty() {
323 if let Some(ref default) = parameter.default {
324 let value = evaluator.evaluate_expression(default)?;
325 positional.push(value.clone());
326 value
327 } else if parameter.kind == ParameterKind::Star {
328 String::new()
329 } else {
330 return Err(Error::Internal {
331 message: "missing parameter without default".to_owned(),
332 });
333 }
334 } else if parameter.kind.is_variadic() {
335 for value in rest {
336 positional.push(value.clone());
337 }
338 let value = rest.to_vec().join(" ");
339 rest = &[];
340 value
341 } else {
342 let value = rest[0].clone();
343 positional.push(value.clone());
344 rest = &rest[1..];
345 value
346 };
347 evaluator.scope.bind(Binding {
348 constant: false,
349 export: parameter.export,
350 file_depth: 0,
351 name: parameter.name,
352 private: false,
353 value,
354 });
355 }
356
357 Ok((evaluator.scope, positional))
358 }
359
360 pub fn new(
361 context: &ExecutionContext<'src, 'run>,
362 is_dependency: bool,
363 scope: &'run Scope<'src, 'run>,
364 ) -> Self {
365 Self {
366 assignments: None,
367 context: *context,
368 is_dependency,
369 scope: scope.child(),
370 }
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 run_error! {
379 name: backtick_code,
380 src: "
381 a:
382 echo {{`f() { return 100; }; f`}}
383 ",
384 args: ["a"],
385 error: Error::Backtick {
386 token,
387 output_error: OutputError::Code(code),
388 },
389 check: {
390 assert_eq!(code, 100);
391 assert_eq!(token.lexeme(), "`f() { return 100; }; f`");
392 }
393 }
394
395 run_error! {
396 name: export_assignment_backtick,
397 src: r#"
398 export exported_variable := "A"
399 b := `echo $exported_variable`
400
401 recipe:
402 echo {{b}}
403 "#,
404 args: ["--quiet", "recipe"],
405 error: Error::Backtick {
406 token,
407 output_error: OutputError::Code(_),
408 },
409 check: {
410 assert_eq!(token.lexeme(), "`echo $exported_variable`");
411 }
412 }
413}