kcl_lib/std/
array.rs

1use indexmap::IndexMap;
2
3use super::{
4    args::{Arg, KwArgs},
5    Args,
6};
7use crate::{
8    errors::{KclError, KclErrorDetails},
9    execution::{
10        kcl_value::{FunctionSource, KclValue},
11        types::RuntimeType,
12        ExecState,
13    },
14    source_range::SourceRange,
15    ExecutorContext,
16};
17
18/// Apply a function to each element of an array.
19pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
20    let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
21    let f: &FunctionSource = args.get_kw_arg("f")?;
22    let new_array = inner_map(array, f, exec_state, &args).await?;
23    Ok(KclValue::HomArray {
24        value: new_array,
25        ty: RuntimeType::any(),
26    })
27}
28
29async fn inner_map<'a>(
30    array: Vec<KclValue>,
31    f: &'a FunctionSource,
32    exec_state: &mut ExecState,
33    args: &'a Args,
34) -> Result<Vec<KclValue>, KclError> {
35    let mut new_array = Vec::with_capacity(array.len());
36    for elem in array {
37        let new_elem = call_map_closure(elem, f, args.source_range, exec_state, &args.ctx).await?;
38        new_array.push(new_elem);
39    }
40    Ok(new_array)
41}
42
43async fn call_map_closure(
44    input: KclValue,
45    map_fn: &FunctionSource,
46    source_range: SourceRange,
47    exec_state: &mut ExecState,
48    ctxt: &ExecutorContext,
49) -> Result<KclValue, KclError> {
50    let kw_args = KwArgs {
51        unlabeled: Some((None, Arg::new(input, source_range))),
52        labeled: Default::default(),
53        errors: Vec::new(),
54    };
55    let args = Args::new_kw(
56        kw_args,
57        source_range,
58        ctxt.clone(),
59        exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
60    );
61    let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
62    let source_ranges = vec![source_range];
63    let output = output.ok_or_else(|| {
64        KclError::Semantic(KclErrorDetails {
65            message: "Map function must return a value".to_string(),
66            source_ranges,
67        })
68    })?;
69    Ok(output)
70}
71
72/// For each item in an array, update a value.
73pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
74    let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
75    let f: &FunctionSource = args.get_kw_arg("f")?;
76    let initial: KclValue = args.get_kw_arg("initial")?;
77    inner_reduce(array, initial, f, exec_state, &args).await
78}
79
80async fn inner_reduce<'a>(
81    array: Vec<KclValue>,
82    initial: KclValue,
83    f: &'a FunctionSource,
84    exec_state: &mut ExecState,
85    args: &'a Args,
86) -> Result<KclValue, KclError> {
87    let mut reduced = initial;
88    for elem in array {
89        reduced = call_reduce_closure(elem, reduced, f, args.source_range, exec_state, &args.ctx).await?;
90    }
91
92    Ok(reduced)
93}
94
95async fn call_reduce_closure(
96    elem: KclValue,
97    accum: KclValue,
98    reduce_fn: &FunctionSource,
99    source_range: SourceRange,
100    exec_state: &mut ExecState,
101    ctxt: &ExecutorContext,
102) -> Result<KclValue, KclError> {
103    // Call the reduce fn for this repetition.
104    let mut labeled = IndexMap::with_capacity(1);
105    labeled.insert("accum".to_string(), Arg::new(accum, source_range));
106    let kw_args = KwArgs {
107        unlabeled: Some((None, Arg::new(elem, source_range))),
108        labeled,
109        errors: Vec::new(),
110    };
111    let reduce_fn_args = Args::new_kw(
112        kw_args,
113        source_range,
114        ctxt.clone(),
115        exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
116    );
117    let transform_fn_return = reduce_fn
118        .call_kw(None, exec_state, ctxt, reduce_fn_args, source_range)
119        .await?;
120
121    // Unpack the returned transform object.
122    let source_ranges = vec![source_range];
123    let out = transform_fn_return.ok_or_else(|| {
124        KclError::Semantic(KclErrorDetails {
125            message: "Reducer function must return a value".to_string(),
126            source_ranges: source_ranges.clone(),
127        })
128    })?;
129    Ok(out)
130}
131
132pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
133    let array = args.get_unlabeled_kw_arg("array")?;
134    let item: KclValue = args.get_kw_arg("item")?;
135
136    let KclValue::HomArray { value: values, ty } = array else {
137        let meta = vec![args.source_range];
138        let actual_type = array.human_friendly_type();
139        return Err(KclError::Semantic(KclErrorDetails {
140            source_ranges: meta,
141            message: format!("You can't push to a value of type {actual_type}, only an array"),
142        }));
143    };
144    let ty = if item.has_type(&ty) {
145        ty
146    } else {
147        // The user pushed an item with a type that differs from the array's
148        // element type.
149        RuntimeType::any()
150    };
151
152    let new_array = inner_push(values, item);
153
154    Ok(KclValue::HomArray { value: new_array, ty })
155}
156
157fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
158    array.push(item);
159    array
160}
161
162pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
163    let array = args.get_unlabeled_kw_arg("array")?;
164    let KclValue::HomArray { value: values, ty } = array else {
165        let meta = vec![args.source_range];
166        let actual_type = array.human_friendly_type();
167        return Err(KclError::Semantic(KclErrorDetails {
168            source_ranges: meta,
169            message: format!("You can't pop from a value of type {actual_type}, only an array"),
170        }));
171    };
172
173    let new_array = inner_pop(values, &args)?;
174    Ok(KclValue::HomArray { value: new_array, ty })
175}
176
177fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
178    if array.is_empty() {
179        return Err(KclError::Semantic(KclErrorDetails {
180            message: "Cannot pop from an empty array".to_string(),
181            source_ranges: vec![args.source_range],
182        }));
183    }
184
185    // Create a new array with all elements except the last one
186    let new_array = array[..array.len() - 1].to_vec();
187
188    Ok(new_array)
189}