kcl_lib/std/
array.rs

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
15/// Apply a function to each element of an array.
16pub 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
69/// For each item in an array, update a value.
70pub 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    // Call the reduce fn for this repetition.
101    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    // Unpack the returned transform object.
119    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            // Tuples are treated as arrays for concatenation.
162            Ok(inner_concat(&left, &left_el_ty, &right, &RuntimeType::any()))
163        }
164        // Any single value is a subtype of an array, so we can treat it as a
165        // single-element array.
166        _ => 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    // Propagate the element type if we can.
191    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}