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