1use indexmap::IndexMap;
2
3use super::{
4 args::{Arg, KwArgs},
5 Args,
6};
7use crate::{
8 errors::{KclError, KclErrorDetails},
9 execution::{
10 kcl_value::{FunctionSource, KclValue},
11 types::RuntimeType,
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 new_array = inner_map(array, f, exec_state, &args).await?;
23 Ok(KclValue::HomArray {
24 value: new_array,
25 ty: RuntimeType::any(),
26 })
27}
28
29async fn inner_map<'a>(
30 array: Vec<KclValue>,
31 f: &'a FunctionSource,
32 exec_state: &mut ExecState,
33 args: &'a Args,
34) -> Result<Vec<KclValue>, KclError> {
35 let mut new_array = Vec::with_capacity(array.len());
36 for elem in array {
37 let new_elem = call_map_closure(elem, f, args.source_range, exec_state, &args.ctx).await?;
38 new_array.push(new_elem);
39 }
40 Ok(new_array)
41}
42
43async fn call_map_closure(
44 input: KclValue,
45 map_fn: &FunctionSource,
46 source_range: SourceRange,
47 exec_state: &mut ExecState,
48 ctxt: &ExecutorContext,
49) -> Result<KclValue, KclError> {
50 let kw_args = KwArgs {
51 unlabeled: Some((None, Arg::new(input, source_range))),
52 labeled: Default::default(),
53 errors: Vec::new(),
54 };
55 let args = Args::new_kw(
56 kw_args,
57 source_range,
58 ctxt.clone(),
59 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
60 );
61 let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
62 let source_ranges = vec![source_range];
63 let output = output.ok_or_else(|| {
64 KclError::Semantic(KclErrorDetails {
65 message: "Map function must return a value".to_string(),
66 source_ranges,
67 })
68 })?;
69 Ok(output)
70}
71
72pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
74 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
75 let f: &FunctionSource = args.get_kw_arg("f")?;
76 let initial: KclValue = args.get_kw_arg("initial")?;
77 inner_reduce(array, initial, f, exec_state, &args).await
78}
79
80async fn inner_reduce<'a>(
81 array: Vec<KclValue>,
82 initial: KclValue,
83 f: &'a FunctionSource,
84 exec_state: &mut ExecState,
85 args: &'a Args,
86) -> Result<KclValue, KclError> {
87 let mut reduced = initial;
88 for elem in array {
89 reduced = call_reduce_closure(elem, reduced, f, args.source_range, exec_state, &args.ctx).await?;
90 }
91
92 Ok(reduced)
93}
94
95async fn call_reduce_closure(
96 elem: KclValue,
97 accum: KclValue,
98 reduce_fn: &FunctionSource,
99 source_range: SourceRange,
100 exec_state: &mut ExecState,
101 ctxt: &ExecutorContext,
102) -> Result<KclValue, KclError> {
103 let mut labeled = IndexMap::with_capacity(1);
105 labeled.insert("accum".to_string(), Arg::new(accum, source_range));
106 let kw_args = KwArgs {
107 unlabeled: Some((None, Arg::new(elem, source_range))),
108 labeled,
109 errors: Vec::new(),
110 };
111 let reduce_fn_args = Args::new_kw(
112 kw_args,
113 source_range,
114 ctxt.clone(),
115 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
116 );
117 let transform_fn_return = reduce_fn
118 .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
119 .await?;
120
121 let source_ranges = vec![source_range];
123 let out = transform_fn_return.ok_or_else(|| {
124 KclError::Semantic(KclErrorDetails {
125 message: "Reducer function must return a value".to_string(),
126 source_ranges: source_ranges.clone(),
127 })
128 })?;
129 Ok(out)
130}
131
132pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
133 let array = args.get_unlabeled_kw_arg("array")?;
134 let item: KclValue = args.get_kw_arg("item")?;
135
136 let KclValue::HomArray { value: values, ty } = array else {
137 let meta = vec![args.source_range];
138 let actual_type = array.human_friendly_type();
139 return Err(KclError::Semantic(KclErrorDetails {
140 source_ranges: meta,
141 message: format!("You can't push to a value of type {actual_type}, only an array"),
142 }));
143 };
144 let ty = if item.has_type(&ty) {
145 ty
146 } else {
147 RuntimeType::any()
150 };
151
152 let new_array = inner_push(values, item);
153
154 Ok(KclValue::HomArray { value: new_array, ty })
155}
156
157fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
158 array.push(item);
159 array
160}
161
162pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
163 let array = args.get_unlabeled_kw_arg("array")?;
164 let KclValue::HomArray { value: values, ty } = array else {
165 let meta = vec![args.source_range];
166 let actual_type = array.human_friendly_type();
167 return Err(KclError::Semantic(KclErrorDetails {
168 source_ranges: meta,
169 message: format!("You can't pop from a value of type {actual_type}, only an array"),
170 }));
171 };
172
173 let new_array = inner_pop(values, &args)?;
174 Ok(KclValue::HomArray { value: new_array, ty })
175}
176
177fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
178 if array.is_empty() {
179 return Err(KclError::Semantic(KclErrorDetails {
180 message: "Cannot pop from an empty array".to_string(),
181 source_ranges: vec![args.source_range],
182 }));
183 }
184
185 let new_array = array[..array.len() - 1].to_vec();
187
188 Ok(new_array)
189}