kcl_lib/std/
array.rs

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
14/// Apply a function to each element of an array.
15pub 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
76/// For each item in an array, update a value.
77pub 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    // Call the reduce fn for this repetition.
108    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    // Unpack the returned transform object.
123    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            // Tuples are treated as arrays for concatenation.
177            Ok(inner_concat(&left, &left_el_ty, &right, &RuntimeType::any()))
178        }
179        // Any single value is a subtype of an array, so we can treat it as a
180        // single-element array.
181        _ => 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    // Propagate the element type if we can.
206    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}