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 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 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 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 Ok(PipelineData::Empty) => (None, None),
196
197 Ok(PipelineData::Value(value, ..)) => {
198 let span = value.span();
199 match value {
200 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 _ => {
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 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}