nu_engine/
closure_eval.rs

1use crate::{
2    eval_block_with_early_return, get_eval_block_with_early_return, EvalBlockWithEarlyReturnFn,
3};
4use nu_protocol::{
5    ast::Block,
6    debugger::{WithDebug, WithoutDebug},
7    engine::{Closure, EngineState, EnvVars, Stack},
8    IntoPipelineData, PipelineData, ShellError, Value,
9};
10use std::{
11    borrow::Cow,
12    collections::{HashMap, HashSet},
13    sync::Arc,
14};
15
16fn eval_fn(debug: bool) -> EvalBlockWithEarlyReturnFn {
17    if debug {
18        eval_block_with_early_return::<WithDebug>
19    } else {
20        eval_block_with_early_return::<WithoutDebug>
21    }
22}
23
24/// [`ClosureEval`] is used to repeatedly evaluate a closure with different values/inputs.
25///
26/// [`ClosureEval`] has a builder API.
27/// It is first created via [`ClosureEval::new`],
28/// then has arguments added via [`ClosureEval::add_arg`],
29/// and then can be run using [`ClosureEval::run_with_input`].
30///
31/// ```no_run
32/// # use nu_protocol::{PipelineData, Value};
33/// # use nu_engine::ClosureEval;
34/// # let engine_state = unimplemented!();
35/// # let stack = unimplemented!();
36/// # let closure = unimplemented!();
37/// let mut closure = ClosureEval::new(engine_state, stack, closure);
38/// let iter = Vec::<Value>::new()
39///     .into_iter()
40///     .map(move |value| closure.add_arg(value).run_with_input(PipelineData::Empty));
41/// ```
42///
43/// Many closures follow a simple, common scheme where the pipeline input and the first argument are the same value.
44/// In this case, use [`ClosureEval::run_with_value`]:
45///
46/// ```no_run
47/// # use nu_protocol::{PipelineData, Value};
48/// # use nu_engine::ClosureEval;
49/// # let engine_state = unimplemented!();
50/// # let stack = unimplemented!();
51/// # let closure = unimplemented!();
52/// let mut closure = ClosureEval::new(engine_state, stack, closure);
53/// let iter = Vec::<Value>::new()
54///     .into_iter()
55///     .map(move |value| closure.run_with_value(value));
56/// ```
57///
58/// Environment isolation and other cleanup is handled by [`ClosureEval`],
59/// so nothing needs to be done following [`ClosureEval::run_with_input`] or [`ClosureEval::run_with_value`].
60pub struct ClosureEval {
61    engine_state: EngineState,
62    stack: Stack,
63    block: Arc<Block>,
64    arg_index: usize,
65    env_vars: Vec<Arc<EnvVars>>,
66    env_hidden: Arc<HashMap<String, HashSet<String>>>,
67    eval: EvalBlockWithEarlyReturnFn,
68}
69
70impl ClosureEval {
71    /// Create a new [`ClosureEval`].
72    pub fn new(engine_state: &EngineState, stack: &Stack, closure: Closure) -> Self {
73        let engine_state = engine_state.clone();
74        let stack = stack.captures_to_stack(closure.captures);
75        let block = engine_state.get_block(closure.block_id).clone();
76        let env_vars = stack.env_vars.clone();
77        let env_hidden = stack.env_hidden.clone();
78        let eval = get_eval_block_with_early_return(&engine_state);
79
80        Self {
81            engine_state,
82            stack,
83            block,
84            arg_index: 0,
85            env_vars,
86            env_hidden,
87            eval,
88        }
89    }
90
91    pub fn new_preserve_out_dest(
92        engine_state: &EngineState,
93        stack: &Stack,
94        closure: Closure,
95    ) -> Self {
96        let engine_state = engine_state.clone();
97        let stack = stack.captures_to_stack_preserve_out_dest(closure.captures);
98        let block = engine_state.get_block(closure.block_id).clone();
99        let env_vars = stack.env_vars.clone();
100        let env_hidden = stack.env_hidden.clone();
101        let eval = get_eval_block_with_early_return(&engine_state);
102
103        Self {
104            engine_state,
105            stack,
106            block,
107            arg_index: 0,
108            env_vars,
109            env_hidden,
110            eval,
111        }
112    }
113
114    /// Sets whether to enable debugging when evaluating the closure.
115    ///
116    /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEval`].
117    pub fn debug(&mut self, debug: bool) -> &mut Self {
118        self.eval = eval_fn(debug);
119        self
120    }
121
122    fn try_add_arg(&mut self, value: Cow<Value>) {
123        if let Some(var_id) = self
124            .block
125            .signature
126            .get_positional(self.arg_index)
127            .and_then(|var| var.var_id)
128        {
129            self.stack.add_var(var_id, value.into_owned());
130            self.arg_index += 1;
131        }
132    }
133
134    /// Add an argument [`Value`] to the closure.
135    ///
136    /// Multiple [`add_arg`](Self::add_arg) calls can be chained together,
137    /// but make sure that arguments are added based on their positional order.
138    pub fn add_arg(&mut self, value: Value) -> &mut Self {
139        self.try_add_arg(Cow::Owned(value));
140        self
141    }
142
143    /// Run the closure, passing the given [`PipelineData`] as input.
144    ///
145    /// Any arguments should be added beforehand via [`add_arg`](Self::add_arg).
146    pub fn run_with_input(&mut self, input: PipelineData) -> Result<PipelineData, ShellError> {
147        self.arg_index = 0;
148        self.stack.with_env(&self.env_vars, &self.env_hidden);
149        (self.eval)(&self.engine_state, &mut self.stack, &self.block, input)
150    }
151
152    /// Run the closure using the given [`Value`] as both the pipeline input and the first argument.
153    ///
154    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
155    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data())`.
156    pub fn run_with_value(&mut self, value: Value) -> Result<PipelineData, ShellError> {
157        self.try_add_arg(Cow::Borrowed(&value));
158        self.run_with_input(value.into_pipeline_data())
159    }
160}
161
162/// [`ClosureEvalOnce`] is used to evaluate a closure a single time.
163///
164/// [`ClosureEvalOnce`] has a builder API.
165/// It is first created via [`ClosureEvalOnce::new`],
166/// then has arguments added via [`ClosureEvalOnce::add_arg`],
167/// and then can be run using [`ClosureEvalOnce::run_with_input`].
168///
169/// ```no_run
170/// # use nu_protocol::{ListStream, PipelineData, PipelineIterator};
171/// # use nu_engine::ClosureEvalOnce;
172/// # let engine_state = unimplemented!();
173/// # let stack = unimplemented!();
174/// # let closure = unimplemented!();
175/// # let value = unimplemented!();
176/// let result = ClosureEvalOnce::new(engine_state, stack, closure)
177///     .add_arg(value)
178///     .run_with_input(PipelineData::Empty);
179/// ```
180///
181/// Many closures follow a simple, common scheme where the pipeline input and the first argument are the same value.
182/// In this case, use [`ClosureEvalOnce::run_with_value`]:
183///
184/// ```no_run
185/// # use nu_protocol::{PipelineData, PipelineIterator};
186/// # use nu_engine::ClosureEvalOnce;
187/// # let engine_state = unimplemented!();
188/// # let stack = unimplemented!();
189/// # let closure = unimplemented!();
190/// # let value = unimplemented!();
191/// let result = ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value);
192/// ```
193pub struct ClosureEvalOnce<'a> {
194    engine_state: &'a EngineState,
195    stack: Stack,
196    block: &'a Block,
197    arg_index: usize,
198    eval: EvalBlockWithEarlyReturnFn,
199}
200
201impl<'a> ClosureEvalOnce<'a> {
202    /// Create a new [`ClosureEvalOnce`].
203    pub fn new(engine_state: &'a EngineState, stack: &Stack, closure: Closure) -> Self {
204        let block = engine_state.get_block(closure.block_id);
205        let eval = get_eval_block_with_early_return(engine_state);
206        Self {
207            engine_state,
208            stack: stack.captures_to_stack(closure.captures),
209            block,
210            arg_index: 0,
211            eval,
212        }
213    }
214
215    pub fn new_preserve_out_dest(
216        engine_state: &'a EngineState,
217        stack: &Stack,
218        closure: Closure,
219    ) -> Self {
220        let block = engine_state.get_block(closure.block_id);
221        let eval = get_eval_block_with_early_return(engine_state);
222        Self {
223            engine_state,
224            stack: stack.captures_to_stack_preserve_out_dest(closure.captures),
225            block,
226            arg_index: 0,
227            eval,
228        }
229    }
230
231    /// Sets whether to enable debugging when evaluating the closure.
232    ///
233    /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEvalOnce`].
234    pub fn debug(mut self, debug: bool) -> Self {
235        self.eval = eval_fn(debug);
236        self
237    }
238
239    fn try_add_arg(&mut self, value: Cow<Value>) {
240        if let Some(var_id) = self
241            .block
242            .signature
243            .get_positional(self.arg_index)
244            .and_then(|var| var.var_id)
245        {
246            self.stack.add_var(var_id, value.into_owned());
247            self.arg_index += 1;
248        }
249    }
250
251    /// Add an argument [`Value`] to the closure.
252    ///
253    /// Multiple [`add_arg`](Self::add_arg) calls can be chained together,
254    /// but make sure that arguments are added based on their positional order.
255    pub fn add_arg(mut self, value: Value) -> Self {
256        self.try_add_arg(Cow::Owned(value));
257        self
258    }
259
260    /// Run the closure, passing the given [`PipelineData`] as input.
261    ///
262    /// Any arguments should be added beforehand via [`add_arg`](Self::add_arg).
263    pub fn run_with_input(mut self, input: PipelineData) -> Result<PipelineData, ShellError> {
264        (self.eval)(self.engine_state, &mut self.stack, self.block, input)
265    }
266
267    /// Run the closure using the given [`Value`] as both the pipeline input and the first argument.
268    ///
269    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
270    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data())`.
271    pub fn run_with_value(mut self, value: Value) -> Result<PipelineData, ShellError> {
272        self.try_add_arg(Cow::Borrowed(&value));
273        self.run_with_input(value.into_pipeline_data())
274    }
275}