1use indexmap::IndexMap;
2
3use crate::{
4 ExecutorContext, SourceRange,
5 errors::{KclError, KclErrorDetails},
6 execution::{
7 ControlFlowKind, ExecState,
8 fn_call::{Arg, Args},
9 kcl_value::{FunctionSource, KclValue},
10 types::RuntimeType,
11 },
12};
13
14pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
16 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
17 let f: FunctionSource = args.get_kw_arg("f", &RuntimeType::function(), exec_state)?;
18 let new_array = inner_map(array, f, exec_state, &args).await?;
19 Ok(KclValue::HomArray {
20 value: new_array,
21 ty: RuntimeType::any(),
22 })
23}
24
25async fn inner_map(
26 array: Vec<KclValue>,
27 f: FunctionSource,
28 exec_state: &mut ExecState,
29 args: &Args,
30) -> Result<Vec<KclValue>, KclError> {
31 let mut new_array = Vec::with_capacity(array.len());
32 for elem in array {
33 let new_elem = call_map_closure(elem, &f, args.source_range, exec_state, &args.ctx).await?;
34 new_array.push(new_elem);
35 }
36 Ok(new_array)
37}
38
39async fn call_map_closure(
40 input: KclValue,
41 map_fn: &FunctionSource,
42 source_range: SourceRange,
43 exec_state: &mut ExecState,
44 ctxt: &ExecutorContext,
45) -> Result<KclValue, KclError> {
46 let args = Args::new(
47 Default::default(),
48 vec![(None, Arg::new(input, source_range))],
49 source_range,
50 exec_state,
51 ctxt.clone(),
52 Some("map closure".to_owned()),
53 );
54 let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
55 let source_ranges = vec![source_range];
56 let output = output.ok_or_else(|| {
57 KclError::new_semantic(KclErrorDetails::new(
58 "Map function must return a value".to_owned(),
59 source_ranges,
60 ))
61 })?;
62 let output = match output.control {
63 ControlFlowKind::Continue => output.into_value(),
64 ControlFlowKind::Exit => {
65 let message = "Early return inside map function is currently not supported".to_owned();
66 debug_assert!(false, "{}", &message);
67 return Err(KclError::new_internal(KclErrorDetails::new(
68 message,
69 vec![source_range],
70 )));
71 }
72 };
73 Ok(output)
74}
75
76pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
78 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
79 let f: FunctionSource = args.get_kw_arg("f", &RuntimeType::function(), exec_state)?;
80 let initial: KclValue = args.get_kw_arg("initial", &RuntimeType::any(), exec_state)?;
81 inner_reduce(array, initial, f, exec_state, &args).await
82}
83
84async fn inner_reduce(
85 array: Vec<KclValue>,
86 initial: KclValue,
87 f: FunctionSource,
88 exec_state: &mut ExecState,
89 args: &Args,
90) -> Result<KclValue, KclError> {
91 let mut reduced = initial;
92 for elem in array {
93 reduced = call_reduce_closure(elem, reduced, &f, args.source_range, exec_state, &args.ctx).await?;
94 }
95
96 Ok(reduced)
97}
98
99async fn call_reduce_closure(
100 elem: KclValue,
101 accum: KclValue,
102 reduce_fn: &FunctionSource,
103 source_range: SourceRange,
104 exec_state: &mut ExecState,
105 ctxt: &ExecutorContext,
106) -> Result<KclValue, KclError> {
107 let mut labeled = IndexMap::with_capacity(1);
109 labeled.insert("accum".to_string(), Arg::new(accum, source_range));
110 let reduce_fn_args = Args::new(
111 labeled,
112 vec![(None, Arg::new(elem, source_range))],
113 source_range,
114 exec_state,
115 ctxt.clone(),
116 Some("reduce closure".to_owned()),
117 );
118 let transform_fn_return = reduce_fn
119 .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
120 .await?;
121
122 let source_ranges = vec![source_range];
124 let out = transform_fn_return.ok_or_else(|| {
125 KclError::new_semantic(KclErrorDetails::new(
126 "Reducer function must return a value".to_string(),
127 source_ranges.clone(),
128 ))
129 })?;
130 let out = match out.control {
131 ControlFlowKind::Continue => out.into_value(),
132 ControlFlowKind::Exit => {
133 let message = "Early return inside reduce function is currently not supported".to_owned();
134 debug_assert!(false, "{}", &message);
135 return Err(KclError::new_internal(KclErrorDetails::new(
136 message,
137 vec![source_range],
138 )));
139 }
140 };
141 Ok(out)
142}
143
144pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
145 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
146 let item: KclValue = args.get_kw_arg("item", &RuntimeType::any(), exec_state)?;
147
148 array.push(item);
149
150 Ok(KclValue::HomArray { value: array, ty })
151}
152
153pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
154 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
155 if array.is_empty() {
156 return Err(KclError::new_semantic(KclErrorDetails::new(
157 "Cannot pop from an empty array".to_string(),
158 vec![args.source_range],
159 )));
160 }
161 array.pop();
162 Ok(KclValue::HomArray { value: array, ty })
163}
164
165pub async fn concat(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
166 let (left, left_el_ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
167 let right_value: KclValue = args.get_kw_arg("items", &RuntimeType::any_array(), exec_state)?;
168
169 match right_value {
170 KclValue::HomArray {
171 value: right,
172 ty: right_el_ty,
173 ..
174 } => Ok(inner_concat(&left, &left_el_ty, &right, &right_el_ty)),
175 KclValue::Tuple { value: right, .. } => {
176 Ok(inner_concat(&left, &left_el_ty, &right, &RuntimeType::any()))
178 }
179 _ => Ok(inner_concat(&left, &left_el_ty, &[right_value], &RuntimeType::any())),
182 }
183}
184
185fn inner_concat(
186 left: &[KclValue],
187 left_el_ty: &RuntimeType,
188 right: &[KclValue],
189 right_el_ty: &RuntimeType,
190) -> KclValue {
191 if left.is_empty() {
192 return KclValue::HomArray {
193 value: right.to_vec(),
194 ty: right_el_ty.clone(),
195 };
196 }
197 if right.is_empty() {
198 return KclValue::HomArray {
199 value: left.to_vec(),
200 ty: left_el_ty.clone(),
201 };
202 }
203 let mut new = left.to_vec();
204 new.extend_from_slice(right);
205 let ty = if right_el_ty.subtype(left_el_ty) {
207 left_el_ty.clone()
208 } else if left_el_ty.subtype(right_el_ty) {
209 right_el_ty.clone()
210 } else {
211 RuntimeType::any()
212 };
213 KclValue::HomArray { value: new, ty }
214}