Skip to main content

nu_command/generators/
generate.rs

1use nu_engine::{ClosureEval, command_prelude::*};
2use nu_protocol::engine::Closure;
3use nu_protocol::shell_error::generic::GenericError;
4
5#[derive(Clone)]
6pub struct Generate;
7
8impl Command for Generate {
9    fn name(&self) -> &str {
10        "generate"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("generate")
15            .input_output_types(vec![
16                (Type::Nothing, Type::list(Type::Any)),
17                (Type::list(Type::Any), Type::list(Type::Any)),
18                (Type::table(), Type::list(Type::Any)),
19                (Type::Range, Type::list(Type::Any)),
20            ])
21            .required(
22                "closure",
23                SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])),
24                "Generator function.",
25            )
26            .optional("initial", SyntaxShape::Any, "Initial value.")
27            .allow_variants_without_examples(true)
28            .category(Category::Generators)
29    }
30
31    fn description(&self) -> &str {
32        "Generate a list of values by successively invoking a closure."
33    }
34
35    fn extra_description(&self) -> &str {
36        "The generator closure accepts a single argument and returns a record
37containing two optional keys: 'out' and 'next'. Each invocation, the 'out'
38value, if present, is added to the stream. If a 'next' key is present, it is
39used as the next argument to the closure, otherwise generation stops.
40
41Additionally, if an input stream is provided, the generator closure accepts two
42arguments. On each invocation an element of the input stream is provided as the
43first argument. The second argument is the `next` value from the last invocation.
44In this case, generation also stops when the input stream stops."
45    }
46
47    fn search_terms(&self) -> Vec<&str> {
48        vec!["unfold", "stream", "yield", "expand", "state", "scan"]
49    }
50
51    fn examples(&self) -> Vec<Example<'_>> {
52        vec![
53            Example {
54                example: "generate {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }} 0",
55                description: "Generate a sequence of numbers",
56                result: Some(Value::list(
57                    vec![
58                        Value::test_int(0),
59                        Value::test_int(2),
60                        Value::test_int(4),
61                        Value::test_int(6),
62                        Value::test_int(8),
63                        Value::test_int(10),
64                    ],
65                    Span::test_data(),
66                )),
67            },
68            Example {
69                example: "generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } [0, 1]",
70                description: "Generate a continuous stream of Fibonacci numbers",
71                result: None,
72            },
73            Example {
74                example: "generate {|fib=[0, 1]| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }",
75                description: "Generate a continuous stream of Fibonacci numbers, using default parameters",
76                result: None,
77            },
78            Example {
79                example: "1..5 | generate {|e, sum=0| let sum = $e + $sum; {out: $sum, next: $sum} }",
80                description: "Generate a running sum of the inputs",
81                result: Some(Value::test_list(vec![
82                    Value::test_int(1),
83                    Value::test_int(3),
84                    Value::test_int(6),
85                    Value::test_int(10),
86                    Value::test_int(15),
87                ])),
88            },
89        ]
90    }
91
92    fn run(
93        &self,
94        engine_state: &EngineState,
95        stack: &mut Stack,
96        call: &Call,
97        input: PipelineData,
98    ) -> Result<PipelineData, ShellError> {
99        let head = call.head;
100        let closure: Closure = call.req(engine_state, stack, 0)?;
101        let initial: Option<Value> = call.opt(engine_state, stack, 1)?;
102        let block = engine_state.get_block(closure.block_id);
103        let mut closure = ClosureEval::new(engine_state, stack, closure);
104
105        match input {
106            PipelineData::Empty => {
107                // A type of Option<S> is used to represent state. Invocation
108                // will stop on None. Using Option<S> allows functions to output
109                // one final value before stopping.
110                let mut state = Some(get_initial_state(initial, &block.signature, call.head)?);
111                let iter = std::iter::from_fn(move || {
112                    let state_arg = state.take()?;
113
114                    let closure_result = closure
115                        .add_arg(state_arg)
116                        .run_with_input(PipelineData::empty());
117                    let (output, next_input) = parse_closure_result(closure_result, head);
118
119                    // We use `state` to control when to stop, not `output`. By wrapping
120                    // it in a `Some`, we allow the generator to output `None` as a valid output
121                    // value.
122                    state = next_input;
123                    Some(output)
124                });
125
126                Ok(iter
127                    .flatten()
128                    .into_pipeline_data(call.head, engine_state.signals().clone()))
129            }
130            input @ (PipelineData::Value(Value::Range { .. }, ..)
131            | PipelineData::Value(Value::List { .. }, ..)
132            | PipelineData::ListStream(..)) => {
133                let mut state = Some(get_initial_state(initial, &block.signature, call.head)?);
134                let iter = input.into_iter().map_while(move |item| {
135                    let state_arg = state.take()?;
136                    let closure_result = closure
137                        .add_arg(item)
138                        .add_arg(state_arg)
139                        .run_with_input(PipelineData::empty());
140                    let (output, next_input) = parse_closure_result(closure_result, head);
141                    state = next_input;
142                    Some(output)
143                });
144                Ok(iter
145                    .flatten()
146                    .into_pipeline_data(call.head, engine_state.signals().clone()))
147            }
148            _ => Err(ShellError::PipelineMismatch {
149                exp_input_type: "nothing".to_string(),
150                dst_span: head,
151                src_span: input.span().unwrap_or(head),
152            }),
153        }
154    }
155}
156
157fn get_initial_state(
158    initial: Option<Value>,
159    signature: &Signature,
160    span: Span,
161) -> Result<Value, ShellError> {
162    match initial {
163        Some(v) => Ok(v),
164        None => {
165            // the initial state should be referred from signature
166            if !signature.optional_positional.is_empty()
167                && signature.optional_positional[0].default_value.is_some()
168            {
169                Ok(signature.optional_positional[0]
170                    .default_value
171                    .clone()
172                    .expect("Already checked default value"))
173            } else {
174                Err(ShellError::Generic(
175                    GenericError::new(
176                        "The initial value is missing",
177                        "Missing initial value",
178                        span,
179                    )
180                    .with_help(
181                        "Provide an <initial> value as an argument to generate, or assign a default value to the closure parameter",
182                    ),
183                ))
184            }
185        }
186    }
187}
188
189fn parse_closure_result(
190    closure_result: Result<PipelineData, ShellError>,
191    head: Span,
192) -> (Option<Value>, Option<Value>) {
193    match closure_result {
194        // no data -> output nothing and stop.
195        Ok(PipelineData::Empty) => (None, None),
196
197        Ok(PipelineData::Value(value, ..)) => {
198            let span = value.span();
199            match value {
200                // {out: ..., next: ...} -> output and continue
201                Value::Record { val, .. } => {
202                    let iter = val.into_owned().into_iter();
203                    let mut out = None;
204                    let mut next = None;
205                    let mut err = None;
206
207                    for (k, v) in iter {
208                        if k.eq_ignore_ascii_case("out") {
209                            out = Some(v);
210                        } else if k.eq_ignore_ascii_case("next") {
211                            next = Some(v);
212                        } else {
213                            let error = ShellError::Generic(GenericError::new(
214                                "Invalid block return",
215                                format!("Unexpected record key '{k}'"),
216                                span,
217                            ));
218                            err = Some(Value::error(error, head));
219                            break;
220                        }
221                    }
222
223                    if err.is_some() {
224                        (err, None)
225                    } else {
226                        (out, next)
227                    }
228                }
229
230                // some other value -> error and stop
231                _ => {
232                    let error = ShellError::Generic(GenericError::new(
233                        "Invalid block return",
234                        format!("Expected record, found {}", value.get_type()),
235                        span,
236                    ));
237
238                    (Some(Value::error(error, head)), None)
239                }
240            }
241        }
242
243        Ok(other) => {
244            let error = other
245                .into_value(head)
246                .map(|val| {
247                    ShellError::Generic(GenericError::new(
248                        "Invalid block return",
249                        format!("Expected record, found {}", val.get_type()),
250                        val.span(),
251                    ))
252                })
253                .unwrap_or_else(|err| err);
254
255            (Some(Value::error(error, head)), None)
256        }
257
258        // error -> error and stop
259        Err(error) => (Some(Value::error(error, head)), None),
260    }
261}
262
263#[cfg(test)]
264mod test {
265    use super::*;
266
267    #[test]
268    fn test_examples() -> nu_test_support::Result {
269        nu_test_support::test().examples(Generate)
270    }
271}