1use indexmap::IndexMap;
2
3use crate::ExecutorContext;
4use crate::SourceRange;
5use crate::errors::KclError;
6use crate::errors::KclErrorDetails;
7use crate::execution::ControlFlowKind;
8use crate::execution::ExecState;
9use crate::execution::fn_call::Arg;
10use crate::execution::fn_call::Args;
11use crate::execution::kcl_value::FunctionSource;
12use crate::execution::kcl_value::KclValue;
13use crate::execution::types::RuntimeType;
14
15pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
17 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
18 let f: FunctionSource = args.get_kw_arg("f", &RuntimeType::function(), exec_state)?;
19 let new_array = inner_map(array, f, exec_state, &args).await?;
20 Ok(KclValue::HomArray {
21 value: new_array,
22 ty: RuntimeType::any(),
23 })
24}
25
26async fn inner_map(
27 array: Vec<KclValue>,
28 f: FunctionSource,
29 exec_state: &mut ExecState,
30 args: &Args,
31) -> Result<Vec<KclValue>, KclError> {
32 let mut new_array = Vec::with_capacity(array.len());
33 for elem in array {
34 let new_elem = call_map_closure(elem, &f, args.source_range, exec_state, &args.ctx).await?;
35 new_array.push(new_elem);
36 }
37 Ok(new_array)
38}
39
40async fn call_map_closure(
41 input: KclValue,
42 map_fn: &FunctionSource,
43 source_range: SourceRange,
44 exec_state: &mut ExecState,
45 ctxt: &ExecutorContext,
46) -> Result<KclValue, KclError> {
47 let args = Args::new(
48 Default::default(),
49 vec![(None, Arg::new(input, source_range))],
50 source_range,
51 exec_state,
52 ctxt.clone(),
53 Some("map closure".to_owned()),
54 );
55 let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
56 let source_ranges = vec![source_range];
57 let output = output.ok_or_else(|| {
58 KclError::new_semantic(KclErrorDetails::new(
59 "Map function must return a value".to_owned(),
60 source_ranges,
61 ))
62 })?;
63 let output = match output.control {
64 ControlFlowKind::Continue => output.into_value(),
65 ControlFlowKind::Exit => {
66 let message = "Early return inside map function is currently not supported".to_owned();
67 debug_assert!(false, "{}", &message);
68 return Err(KclError::new_internal(KclErrorDetails::new(
69 message,
70 vec![source_range],
71 )));
72 }
73 };
74 Ok(output)
75}
76
77pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
79 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
80 let f: FunctionSource = args.get_kw_arg("f", &RuntimeType::function(), exec_state)?;
81 let initial: KclValue = args.get_kw_arg("initial", &RuntimeType::any(), exec_state)?;
82 inner_reduce(array, initial, f, exec_state, &args).await
83}
84
85async fn inner_reduce(
86 array: Vec<KclValue>,
87 initial: KclValue,
88 f: FunctionSource,
89 exec_state: &mut ExecState,
90 args: &Args,
91) -> Result<KclValue, KclError> {
92 let mut reduced = initial;
93 for elem in array {
94 reduced = call_reduce_closure(elem, reduced, &f, args.source_range, exec_state, &args.ctx).await?;
95 }
96
97 Ok(reduced)
98}
99
100async fn call_reduce_closure(
101 elem: KclValue,
102 accum: KclValue,
103 reduce_fn: &FunctionSource,
104 source_range: SourceRange,
105 exec_state: &mut ExecState,
106 ctxt: &ExecutorContext,
107) -> Result<KclValue, KclError> {
108 let mut labeled = IndexMap::with_capacity(1);
110 labeled.insert("accum".to_string(), Arg::new(accum, source_range));
111 let reduce_fn_args = Args::new(
112 labeled,
113 vec![(None, Arg::new(elem, source_range))],
114 source_range,
115 exec_state,
116 ctxt.clone(),
117 Some("reduce closure".to_owned()),
118 );
119 let transform_fn_return = reduce_fn
120 .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
121 .await?;
122
123 let source_ranges = vec![source_range];
125 let out = transform_fn_return.ok_or_else(|| {
126 KclError::new_semantic(KclErrorDetails::new(
127 "Reducer function must return a value".to_string(),
128 source_ranges.clone(),
129 ))
130 })?;
131 let out = match out.control {
132 ControlFlowKind::Continue => out.into_value(),
133 ControlFlowKind::Exit => {
134 let message = "Early return inside reduce function is currently not supported".to_owned();
135 debug_assert!(false, "{}", &message);
136 return Err(KclError::new_internal(KclErrorDetails::new(
137 message,
138 vec![source_range],
139 )));
140 }
141 };
142 Ok(out)
143}
144
145pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
146 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
147 let item: KclValue = args.get_kw_arg("item", &RuntimeType::any(), exec_state)?;
148
149 array.push(item);
150
151 Ok(KclValue::HomArray { value: array, ty })
152}
153
154pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
155 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
156 if array.is_empty() {
157 return Err(KclError::new_semantic(KclErrorDetails::new(
158 "Cannot pop from an empty array".to_string(),
159 vec![args.source_range],
160 )));
161 }
162 array.pop();
163 Ok(KclValue::HomArray { value: array, ty })
164}
165
166pub async fn concat(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
167 let (left, left_el_ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
168 let right_value: KclValue = args.get_kw_arg("items", &RuntimeType::any_array(), exec_state)?;
169
170 match right_value {
171 KclValue::HomArray {
172 value: right,
173 ty: right_el_ty,
174 ..
175 } => Ok(inner_concat(&left, &left_el_ty, &right, &right_el_ty)),
176 KclValue::Tuple { value: right, .. } => {
177 Ok(inner_concat(&left, &left_el_ty, &right, &RuntimeType::any()))
179 }
180 _ => Ok(inner_concat(&left, &left_el_ty, &[right_value], &RuntimeType::any())),
183 }
184}
185
186fn inner_concat(
187 left: &[KclValue],
188 left_el_ty: &RuntimeType,
189 right: &[KclValue],
190 right_el_ty: &RuntimeType,
191) -> KclValue {
192 if left.is_empty() {
193 return KclValue::HomArray {
194 value: right.to_vec(),
195 ty: right_el_ty.clone(),
196 };
197 }
198 if right.is_empty() {
199 return KclValue::HomArray {
200 value: left.to_vec(),
201 ty: left_el_ty.clone(),
202 };
203 }
204 let mut new = left.to_vec();
205 new.extend_from_slice(right);
206 let ty = if right_el_ty.subtype(left_el_ty) {
208 left_el_ty.clone()
209 } else if left_el_ty.subtype(right_el_ty) {
210 right_el_ty.clone()
211 } else {
212 RuntimeType::any()
213 };
214 KclValue::HomArray { value: new, ty }
215}
216
217pub async fn slice(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
218 let (array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
219 let start: Option<i64> = args.get_kw_arg_opt("start", &RuntimeType::count(), exec_state)?;
220 let end: Option<i64> = args.get_kw_arg_opt("end", &RuntimeType::count(), exec_state)?;
221
222 if start.is_none() && end.is_none() {
223 return Err(KclError::new_semantic(KclErrorDetails::new(
224 "Either `start` or `end` must be provided".to_owned(),
225 vec![args.source_range],
226 )));
227 }
228
229 let Ok(len) = i64::try_from(array.len()) else {
230 return Err(KclError::new_semantic(KclErrorDetails::new(
231 format!("Array length {} exceeds maximum supported length", array.len()),
232 vec![args.source_range],
233 )));
234 };
235 let mut computed_start = start.unwrap_or(0);
236 let mut computed_end = end.unwrap_or(len);
237
238 if computed_start < 0 {
240 computed_start += len;
241 }
242 if computed_end < 0 {
243 computed_end += len;
244 }
245
246 fn empty_slice(ty: RuntimeType) -> KclValue {
247 KclValue::HomArray { value: Vec::new(), ty }
248 }
249
250 if computed_start < 0 {
251 computed_start = 0;
252 }
253 if computed_start >= len {
254 return Ok(empty_slice(ty));
255 }
256 if computed_end > len {
257 computed_end = len;
258 }
259 if computed_end < 0 {
260 return Ok(empty_slice(ty));
261 }
262
263 if computed_start >= computed_end {
264 return Ok(empty_slice(ty));
265 }
266
267 let Some(sliced) = array.get(computed_start as usize..computed_end as usize) else {
268 let message = "Failed to compute array slice".to_owned();
269 debug_assert!(false, "{message}");
270 return Err(KclError::new_internal(KclErrorDetails::new(
271 message,
272 vec![args.source_range],
273 )));
274 };
275 Ok(KclValue::HomArray {
276 value: sliced.to_vec(),
277 ty,
278 })
279}
280
281pub async fn flatten(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
282 let array_value: KclValue = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
283 let mut flattened = Vec::new();
284
285 let (array, original_ty) = match array_value {
286 KclValue::HomArray { value, ty, .. } => (value, ty),
287 KclValue::Tuple { value, .. } => (value, RuntimeType::any()),
288 _ => (vec![array_value], RuntimeType::any()),
289 };
290 for elem in array {
291 match elem {
292 KclValue::HomArray { value, .. } => flattened.extend(value),
293 KclValue::Tuple { value, .. } => flattened.extend(value),
294 _ => flattened.push(elem),
295 }
296 }
297
298 let ty = infer_flattened_type(original_ty, &flattened);
299 Ok(KclValue::HomArray { value: flattened, ty })
300}
301
302fn infer_flattened_type(original_ty: RuntimeType, values: &[KclValue]) -> RuntimeType {
307 for value in values {
308 if !value.has_type(&original_ty) {
309 return RuntimeType::any();
310 };
311 }
312
313 original_ty
314}