1use indexmap::IndexMap;
2
3use crate::{
4 ExecutorContext,
5 errors::{KclError, KclErrorDetails},
6 execution::{
7 ExecState,
8 fn_call::{Arg, Args, KwArgs},
9 kcl_value::{FunctionSource, KclValue},
10 types::RuntimeType,
11 },
12 source_range::SourceRange,
13};
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 kw_args = KwArgs {
48 unlabeled: Some((None, Arg::new(input, source_range))),
49 labeled: Default::default(),
50 errors: Vec::new(),
51 };
52 let args = Args::new_kw(
53 kw_args,
54 source_range,
55 ctxt.clone(),
56 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
57 );
58 let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
59 let source_ranges = vec![source_range];
60 let output = output.ok_or_else(|| {
61 KclError::new_semantic(KclErrorDetails::new(
62 "Map function must return a value".to_owned(),
63 source_ranges,
64 ))
65 })?;
66 Ok(output)
67}
68
69pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
71 let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array", &RuntimeType::any_array(), exec_state)?;
72 let f: FunctionSource = args.get_kw_arg("f", &RuntimeType::function(), exec_state)?;
73 let initial: KclValue = args.get_kw_arg("initial", &RuntimeType::any(), exec_state)?;
74 inner_reduce(array, initial, f, exec_state, &args).await
75}
76
77async fn inner_reduce(
78 array: Vec<KclValue>,
79 initial: KclValue,
80 f: FunctionSource,
81 exec_state: &mut ExecState,
82 args: &Args,
83) -> Result<KclValue, KclError> {
84 let mut reduced = initial;
85 for elem in array {
86 reduced = call_reduce_closure(elem, reduced, &f, args.source_range, exec_state, &args.ctx).await?;
87 }
88
89 Ok(reduced)
90}
91
92async fn call_reduce_closure(
93 elem: KclValue,
94 accum: KclValue,
95 reduce_fn: &FunctionSource,
96 source_range: SourceRange,
97 exec_state: &mut ExecState,
98 ctxt: &ExecutorContext,
99) -> Result<KclValue, KclError> {
100 let mut labeled = IndexMap::with_capacity(1);
102 labeled.insert("accum".to_string(), Arg::new(accum, source_range));
103 let kw_args = KwArgs {
104 unlabeled: Some((None, Arg::new(elem, source_range))),
105 labeled,
106 errors: Vec::new(),
107 };
108 let reduce_fn_args = Args::new_kw(
109 kw_args,
110 source_range,
111 ctxt.clone(),
112 exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
113 );
114 let transform_fn_return = reduce_fn
115 .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
116 .await?;
117
118 let source_ranges = vec![source_range];
120 let out = transform_fn_return.ok_or_else(|| {
121 KclError::new_semantic(KclErrorDetails::new(
122 "Reducer function must return a value".to_string(),
123 source_ranges.clone(),
124 ))
125 })?;
126 Ok(out)
127}
128
129pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
130 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
131 let item: KclValue = args.get_kw_arg("item", &RuntimeType::any(), exec_state)?;
132
133 array.push(item);
134
135 Ok(KclValue::HomArray { value: array, ty })
136}
137
138pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
139 let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
140 if array.is_empty() {
141 return Err(KclError::new_semantic(KclErrorDetails::new(
142 "Cannot pop from an empty array".to_string(),
143 vec![args.source_range],
144 )));
145 }
146 array.pop();
147 Ok(KclValue::HomArray { value: array, ty })
148}
149
150pub async fn concat(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
151 let (left, left_el_ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
152 let right_value: KclValue = args.get_kw_arg("items", &RuntimeType::any_array(), exec_state)?;
153
154 match right_value {
155 KclValue::HomArray {
156 value: right,
157 ty: right_el_ty,
158 ..
159 } => Ok(inner_concat(&left, &left_el_ty, &right, &right_el_ty)),
160 KclValue::Tuple { value: right, .. } => {
161 Ok(inner_concat(&left, &left_el_ty, &right, &RuntimeType::any()))
163 }
164 _ => Ok(inner_concat(&left, &left_el_ty, &[right_value], &RuntimeType::any())),
167 }
168}
169
170fn inner_concat(
171 left: &[KclValue],
172 left_el_ty: &RuntimeType,
173 right: &[KclValue],
174 right_el_ty: &RuntimeType,
175) -> KclValue {
176 if left.is_empty() {
177 return KclValue::HomArray {
178 value: right.to_vec(),
179 ty: right_el_ty.clone(),
180 };
181 }
182 if right.is_empty() {
183 return KclValue::HomArray {
184 value: left.to_vec(),
185 ty: left_el_ty.clone(),
186 };
187 }
188 let mut new = left.to_vec();
189 new.extend_from_slice(right);
190 let ty = if right_el_ty.subtype(left_el_ty) {
192 left_el_ty.clone()
193 } else if left_el_ty.subtype(right_el_ty) {
194 right_el_ty.clone()
195 } else {
196 RuntimeType::any()
197 };
198 KclValue::HomArray { value: new, ty }
199}