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