nu_engine/
closure_eval.rs

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