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}