pub_just/
evaluator.rs

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}