nu_command/generators/
generate.rs

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