Skip to main content

nu_engine/
closure_eval.rs

1use crate::{eval::CallEval, get_eval_block_with_early_return};
2use nu_protocol::{
3    IntoPipelineData, PipelineData, PipelineMetadata, ShellError, Span, Value,
4    ast::Block,
5    engine::{Closure, EngineState, EnvName, EnvVars, Stack},
6};
7use std::{
8    borrow::Cow,
9    collections::{HashMap, HashSet},
10    sync::Arc,
11};
12
13/// [`ClosureEval`] is used to repeatedly evaluate a closure with different values/inputs.
14///
15/// [`ClosureEval`] has a builder API.
16/// It is first created via [`ClosureEval::new`],
17/// then has arguments added via [`ClosureEval::add_arg`],
18/// and then can be run using [`ClosureEval::run_with_input`].
19///
20/// ```no_run
21/// # use nu_protocol::{PipelineData, Value};
22/// # use nu_engine::ClosureEval;
23/// # let engine_state = unimplemented!();
24/// # let stack = unimplemented!();
25/// # let closure = unimplemented!();
26/// let mut closure = ClosureEval::new(engine_state, stack, closure);
27/// let iter = Vec::<Value>::new()
28///     .into_iter()
29///     .map(move |value| closure.add_arg(value).unwrap().run_with_input(PipelineData::empty()));
30/// ```
31///
32/// Many closures follow a simple, common scheme where the pipeline input and the first argument are the same value.
33/// In this case, use [`ClosureEval::run_with_value`]:
34///
35/// ```no_run
36/// # use nu_protocol::{PipelineData, Value};
37/// # use nu_engine::ClosureEval;
38/// # let engine_state = unimplemented!();
39/// # let stack = unimplemented!();
40/// # let closure = unimplemented!();
41/// let mut closure = ClosureEval::new(engine_state, stack, closure);
42/// let iter = Vec::<Value>::new()
43///     .into_iter()
44///     .map(move |value| closure.run_with_value(value));
45/// ```
46///
47/// Environment isolation and other cleanup is handled by [`ClosureEval`],
48/// so nothing needs to be done following [`ClosureEval::run_with_input`] or [`ClosureEval::run_with_value`].
49///
50/// In contrast to [`ClosureEvalOnce`], [`ClosureEval`] holds an owned copy of the
51/// [`EngineState`] supplied. This makes it possible to return it from a function that
52/// only has a borrowed [`EngineState`] reference, such as the `run` function of a
53/// [`nu_engine::command_prelude::Command`] implementation.
54#[derive(Clone)]
55pub struct ClosureEval {
56    engine_state: EngineState,
57    block: Arc<Block>,
58    env_vars: Vec<Arc<EnvVars>>,
59    env_hidden: Arc<HashMap<String, HashSet<EnvName>>>,
60    call_eval: CallEval,
61}
62
63impl ClosureEval {
64    /// Create a new [`ClosureEval`].
65    pub fn new(engine_state: &EngineState, stack: &Stack, closure: Closure) -> Self {
66        let engine_state = engine_state.clone();
67        let callee_stack = stack.captures_to_stack(closure.captures);
68        let block = engine_state.get_block(closure.block_id).clone();
69        let env_vars = stack.env_vars.clone();
70        let env_hidden = stack.env_hidden.clone();
71        let call_eval = CallEval::new(
72            callee_stack,
73            Span::unknown(),
74            block.span.unwrap_or(Span::unknown()),
75            get_eval_block_with_early_return(&engine_state),
76        );
77
78        Self {
79            engine_state,
80            block,
81            env_vars,
82            env_hidden,
83            call_eval,
84        }
85    }
86
87    pub fn new_preserve_out_dest(
88        engine_state: &EngineState,
89        stack: &Stack,
90        closure: Closure,
91    ) -> Self {
92        let engine_state = engine_state.clone();
93        let callee_stack = stack.captures_to_stack_preserve_out_dest(closure.captures);
94        let block = engine_state.get_block(closure.block_id).clone();
95        let env_vars = stack.env_vars.clone();
96        let env_hidden = stack.env_hidden.clone();
97        let call_eval = CallEval::new(
98            callee_stack,
99            Span::unknown(),
100            block.span.unwrap_or(Span::unknown()),
101            get_eval_block_with_early_return(&engine_state),
102        );
103
104        Self {
105            engine_state,
106            block,
107            env_vars,
108            env_hidden,
109            call_eval,
110        }
111    }
112
113    /// Sets whether to enable debugging when evaluating the closure.
114    ///
115    /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEval`].
116    pub fn debug(&mut self, debug: bool) -> &mut Self {
117        self.call_eval.debug(debug);
118        self
119    }
120
121    /// Add an argument [`Value`] to the closure.
122    ///
123    /// Multiple [`add_arg`](Self::add_arg) calls can be chained together,
124    /// but make sure that arguments are added based on their positional order.
125    pub fn add_arg(&mut self, value: Value) -> Result<&mut Self, ShellError> {
126        self.call_eval
127            .add_positional(&self.block.signature, Cow::Owned(value))?;
128        Ok(self)
129    }
130
131    /// Run the closure, passing the given [`PipelineData`] as input.
132    ///
133    /// Any arguments should be added beforehand via [`add_arg`](Self::add_arg).
134    pub fn run_with_input(&mut self, input: PipelineData) -> Result<PipelineData, ShellError> {
135        self.call_eval
136            .with_env(&self.env_vars, &self.env_hidden)
137            .run(&self.engine_state, &self.block, input)
138    }
139
140    /// Run the closure using the given [`Value`] as both the pipeline input and the first argument.
141    ///
142    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
143    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data())`.
144    pub fn run_with_value(&mut self, value: Value) -> Result<PipelineData, ShellError> {
145        self.call_eval
146            .add_positional(&self.block.signature, Cow::Borrowed(&value))?;
147        self.run_with_input(value.into_pipeline_data())
148    }
149
150    /// Run the closure using the given [`Value`] as both the pipeline input with metadata and the first argument.
151    ///
152    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
153    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data_with_metadata(metadata))`.
154    pub fn run_with_value_with_metadata(
155        &mut self,
156        value: Value,
157        metadata: Option<PipelineMetadata>,
158    ) -> Result<PipelineData, ShellError> {
159        self.call_eval
160            .add_positional(&self.block.signature, Cow::Borrowed(&value))?;
161        self.run_with_input(value.into_pipeline_data_with_metadata(metadata))
162    }
163}
164
165/// [`ClosureEvalOnce`] is used to evaluate a closure a single time.
166///
167/// [`ClosureEvalOnce`] has a builder API.
168/// It is first created via [`ClosureEvalOnce::new`],
169/// then has arguments added via [`ClosureEvalOnce::add_arg`],
170/// and then can be run using [`ClosureEvalOnce::run_with_input`].
171///
172/// ```no_run
173/// # use nu_protocol::{ListStream, PipelineData, PipelineIterator};
174/// # use nu_engine::ClosureEvalOnce;
175/// # let engine_state = unimplemented!();
176/// # let stack = unimplemented!();
177/// # let closure = unimplemented!();
178/// # let value = unimplemented!();
179/// let result = ClosureEvalOnce::new(engine_state, stack, closure)
180///     .add_arg(value)
181///     .unwrap()
182///     .run_with_input(PipelineData::empty());
183/// ```
184///
185/// Many closures follow a simple, common scheme where the pipeline input and the first argument are the same value.
186/// In this case, use [`ClosureEvalOnce::run_with_value`]:
187///
188/// ```no_run
189/// # use nu_protocol::{PipelineData, PipelineIterator};
190/// # use nu_engine::ClosureEvalOnce;
191/// # let engine_state = unimplemented!();
192/// # let stack = unimplemented!();
193/// # let closure = unimplemented!();
194/// # let value = unimplemented!();
195/// let result = ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value);
196/// ```
197///
198/// In contrast to [`ClosureEval`], the lifetime of [`ClosureEvalOnce`] is bound
199/// to that of the supplied [`EngineState`] reference, which makes it more light-weight.
200pub struct ClosureEvalOnce<'a> {
201    engine_state: &'a EngineState,
202    block: &'a Block,
203    call_eval: CallEval,
204    caller_stack: Option<&'a mut Stack>,
205}
206
207impl<'a> ClosureEvalOnce<'a> {
208    /// Create a new [`ClosureEvalOnce`].
209    pub fn new(engine_state: &'a EngineState, stack: &Stack, closure: Closure) -> Self {
210        let block = engine_state.get_block(closure.block_id);
211        let callee_stack = stack.captures_to_stack(closure.captures);
212        let call_eval = CallEval::new(
213            callee_stack,
214            Span::unknown(),
215            block.span.unwrap_or(Span::unknown()),
216            get_eval_block_with_early_return(engine_state),
217        );
218        Self {
219            engine_state,
220            block,
221            call_eval,
222            caller_stack: None,
223        }
224    }
225
226    pub fn new_preserve_out_dest(
227        engine_state: &'a EngineState,
228        stack: &Stack,
229        closure: Closure,
230    ) -> Self {
231        let block = engine_state.get_block(closure.block_id);
232        let callee_stack = stack.captures_to_stack_preserve_out_dest(closure.captures);
233        let call_eval = CallEval::new(
234            callee_stack,
235            Span::unknown(),
236            block.span.unwrap_or(Span::unknown()),
237            get_eval_block_with_early_return(engine_state),
238        );
239        Self {
240            engine_state,
241            block,
242            call_eval,
243            caller_stack: None,
244        }
245    }
246
247    pub fn new_env_preserve_out_dest(
248        engine_state: &'a EngineState,
249        stack: &'a mut Stack,
250        closure: Closure,
251    ) -> Self {
252        let block = engine_state.get_block(closure.block_id);
253        let callee_stack = stack.captures_to_stack_preserve_out_dest(closure.captures);
254        let call_eval = CallEval::new(
255            callee_stack,
256            Span::unknown(),
257            block.span.unwrap_or(Span::unknown()),
258            get_eval_block_with_early_return(engine_state),
259        );
260        Self {
261            engine_state,
262            block,
263            call_eval,
264            caller_stack: Some(stack),
265        }
266    }
267
268    /// Sets whether to enable debugging when evaluating the closure.
269    ///
270    /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEvalOnce`].
271    pub fn debug(mut self, debug: bool) -> Self {
272        self.call_eval.debug(debug);
273        self
274    }
275
276    /// Add an argument [`Value`] to the closure.
277    ///
278    /// Multiple [`add_arg`](Self::add_arg) calls can be chained together,
279    /// but make sure that arguments are added based on their positional order.
280    pub fn add_arg(mut self, value: Value) -> Result<Self, ShellError> {
281        self.call_eval
282            .add_positional(&self.block.signature, Cow::Owned(value))?;
283        Ok(self)
284    }
285
286    /// Add a list of argument [`Value`]s to the closure.
287    pub fn add_args(mut self, values: Vec<Value>) -> Result<Self, ShellError> {
288        for value in values {
289            self.call_eval
290                .add_positional(&self.block.signature, Cow::Owned(value))?;
291        }
292        Ok(self)
293    }
294
295    /// Run the closure, passing the given [`PipelineData`] as input.
296    ///
297    /// Any arguments should be added beforehand via [`add_arg`](Self::add_arg).
298    pub fn run_with_input(mut self, input: PipelineData) -> Result<PipelineData, ShellError> {
299        let result = self.call_eval.run(self.engine_state, self.block, input);
300        if let Some(caller) = self.caller_stack {
301            self.call_eval.redirect_env(self.engine_state, caller);
302        }
303        result
304    }
305
306    /// Run the closure using the given [`Value`] as both the pipeline input and the first argument.
307    ///
308    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
309    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data())`.
310    pub fn run_with_value(mut self, value: Value) -> Result<PipelineData, ShellError> {
311        self.call_eval
312            .add_positional(&self.block.signature, Cow::Borrowed(&value))?;
313        self.run_with_input(value.into_pipeline_data())
314    }
315
316    /// Run the closure using the given [`Value`] as both the pipeline input with metadata and the first argument.
317    ///
318    /// Using this function after or in combination with [`add_arg`](Self::add_arg) is most likely an error.
319    /// This function is equivalent to `self.add_arg(value)` followed by `self.run_with_input(value.into_pipeline_data_with_metadata(metadata))`.
320    pub fn run_with_value_with_metadata(
321        mut self,
322        value: Value,
323        metadata: Option<PipelineMetadata>,
324    ) -> Result<PipelineData, ShellError> {
325        self.call_eval
326            .add_positional(&self.block.signature, Cow::Borrowed(&value))?;
327        self.run_with_input(value.into_pipeline_data_with_metadata(metadata))
328    }
329}