1use indexmap::IndexMap;
2use kcl_derive_docs::stdlib;
3
4use super::{
5 args::{Arg, KwArgs},
6 Args,
7};
8use crate::{
9 errors::{KclError, KclErrorDetails},
10 execution::{
11 kcl_value::{FunctionSource, KclValue},
12 ExecState,
13 },
14 source_range::SourceRange,
15 ExecutorContext,
16};
17
18pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
21 let f: &FunctionSource = args.get_kw_arg("f")?;
22 let meta = vec![args.source_range.into()];
23 let new_array = inner_map(array, f, exec_state, &args).await?;
24 Ok(KclValue::MixedArray { value: new_array, meta })
25}
26
27#[stdlib {
58 name = "map",
59 keywords = true,
60 unlabeled_first = true,
61 args = {
62 array = { docs = "Input array. The output array is this input array, but every element has had the function `f` run on it." },
63 f = { docs = "A function. The output array is just the input array, but `f` has been run on every item." },
64 },
65 tags = ["array"]
66}]
67async fn inner_map<'a>(
68 array: Vec<KclValue>,
69 f: &'a FunctionSource,
70 exec_state: &mut ExecState,
71 args: &'a Args,
72) -> Result<Vec<KclValue>, KclError> {
73 let mut new_array = Vec::with_capacity(array.len());
74 for elem in array {
75 let new_elem = call_map_closure(elem, f, args.source_range, exec_state, &args.ctx).await?;
76 new_array.push(new_elem);
77 }
78 Ok(new_array)
79}
80
81async fn call_map_closure(
82 input: KclValue,
83 map_fn: &FunctionSource,
84 source_range: SourceRange,
85 exec_state: &mut ExecState,
86 ctxt: &ExecutorContext,
87) -> Result<KclValue, KclError> {
88 let kw_args = KwArgs {
89 unlabeled: Some(Arg::new(input, source_range)),
90 labeled: Default::default(),
91 };
92 let args = Args::new_kw(
93 kw_args,
94 source_range,
95 ctxt.clone(),
96 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
97 );
98 let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
99 let source_ranges = vec![source_range];
100 let output = output.ok_or_else(|| {
101 KclError::Semantic(KclErrorDetails {
102 message: "Map function must return a value".to_string(),
103 source_ranges,
104 })
105 })?;
106 Ok(output)
107}
108
109pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
111 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
112 let f: &FunctionSource = args.get_kw_arg("f")?;
113 let initial: KclValue = args.get_kw_arg("initial")?;
114 inner_reduce(array, initial, f, exec_state, &args).await
115}
116
117#[stdlib {
197 name = "reduce",
198 keywords = true,
199 unlabeled_first = true,
200 args = {
201 array = { docs = "Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run." },
202 initial = { docs = "The first time `f` is run, it will be called with the first item of `array` and this initial starting value."},
203 f = { docs = "Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`." },
204 },
205 tags = ["array"]
206}]
207async fn inner_reduce<'a>(
208 array: Vec<KclValue>,
209 initial: KclValue,
210 f: &'a FunctionSource,
211 exec_state: &mut ExecState,
212 args: &'a Args,
213) -> Result<KclValue, KclError> {
214 let mut reduced = initial;
215 for elem in array {
216 reduced = call_reduce_closure(elem, reduced, f, args.source_range, exec_state, &args.ctx).await?;
217 }
218
219 Ok(reduced)
220}
221
222async fn call_reduce_closure(
223 elem: KclValue,
224 accum: KclValue,
225 reduce_fn: &FunctionSource,
226 source_range: SourceRange,
227 exec_state: &mut ExecState,
228 ctxt: &ExecutorContext,
229) -> Result<KclValue, KclError> {
230 let mut labeled = IndexMap::with_capacity(1);
232 labeled.insert("accum".to_string(), Arg::new(accum, source_range));
233 let kw_args = KwArgs {
234 unlabeled: Some(Arg::new(elem, source_range)),
235 labeled,
236 };
237 let reduce_fn_args = Args::new_kw(
238 kw_args,
239 source_range,
240 ctxt.clone(),
241 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
242 );
243 let transform_fn_return = reduce_fn
244 .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
245 .await?;
246
247 let source_ranges = vec![source_range];
249 let out = transform_fn_return.ok_or_else(|| {
250 KclError::Semantic(KclErrorDetails {
251 message: "Reducer function must return a value".to_string(),
252 source_ranges: source_ranges.clone(),
253 })
254 })?;
255 Ok(out)
256}
257
258#[stdlib {
268 name = "push",
269 keywords = true,
270 unlabeled_first = true,
271 args = {
272 array = { docs = "The array which you're adding a new item to." },
273 item = { docs = "The new item to add to the array" },
274 },
275 tags = ["array"]
276}]
277async fn inner_push(mut array: Vec<KclValue>, item: KclValue, args: &Args) -> Result<KclValue, KclError> {
278 array.push(item);
279 Ok(KclValue::MixedArray {
280 value: array,
281 meta: vec![args.source_range.into()],
282 })
283}
284
285pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
286 let val: KclValue = args.get_unlabeled_kw_arg("array")?;
288 let item = args.get_kw_arg("item")?;
289
290 let meta = vec![args.source_range];
291 let KclValue::MixedArray { value: array, meta: _ } = val else {
292 let actual_type = val.human_friendly_type();
293 return Err(KclError::Semantic(KclErrorDetails {
294 source_ranges: meta,
295 message: format!("You can't push to a value of type {actual_type}, only an array"),
296 }));
297 };
298 inner_push(array, item, &args).await
299}
300
301#[stdlib {
313 name = "pop",
314 keywords = true,
315 unlabeled_first = true,
316 args = {
317 array = { docs = "The array to pop from. Must not be empty."},
318 },
319 tags = ["array"]
320}]
321async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclError> {
322 if array.is_empty() {
323 return Err(KclError::Semantic(KclErrorDetails {
324 message: "Cannot pop from an empty array".to_string(),
325 source_ranges: vec![args.source_range],
326 }));
327 }
328
329 let new_array = array[..array.len() - 1].to_vec();
331
332 Ok(KclValue::MixedArray {
333 value: new_array,
334 meta: vec![args.source_range.into()],
335 })
336}
337
338pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
339 let val = args.get_unlabeled_kw_arg("array")?;
341
342 let meta = vec![args.source_range];
343 let KclValue::MixedArray { value: array, meta: _ } = val else {
344 let actual_type = val.human_friendly_type();
345 return Err(KclError::Semantic(KclErrorDetails {
346 source_ranges: meta,
347 message: format!("You can't pop from a value of type {actual_type}, only an array"),
348 }));
349 };
350
351 inner_pop(array, &args).await
352}