KCL: Reduce can take and return any KCL values (#4094)

Previously it only took Array of Number and could only return Sketch.

Now it has been unshackled from the chains of poor type signatures.
This commit is contained in:
Adam Chalmers
2024-10-04 13:26:16 -05:00
committed by GitHub
parent 4e0dd12f5a
commit ec8cacb788
7 changed files with 12576 additions and 1384 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -787,12 +787,14 @@ fn test_generate_stdlib_json_schema() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let mut json_data = vec![];
for key in combined.keys().sorted() {
let json_data: Vec<_> = combined
.keys()
.sorted()
.map(|key| {
let internal_fn = combined.get(key).unwrap();
json_data.push(internal_fn.to_json().unwrap());
}
internal_fn.to_json().unwrap()
})
.collect();
expectorate::assert_contents(
"../../../docs/kcl/std.json",
&serde_json::to_string_pretty(&json_data).unwrap(),

View File

@ -83,6 +83,8 @@ impl StdLibFnArg {
return Ok(Some((index, format!("${{{}:{}}}", index, "myTag"))));
} else if self.type_ == "[KclValue]" && self.required {
return Ok(Some((index, format!("${{{}:{}}}", index, "[0..9]"))));
} else if self.type_ == "KclValue" && self.required {
return Ok(Some((index, format!("${{{}:{}}}", index, "3"))));
}
get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index)
}

View File

@ -4,7 +4,7 @@ use serde_json::Value as JValue;
use super::{args::FromArgs, Args, FnAsArg};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, Sketch, SourceRange, UserVal},
executor::{ExecState, KclValue, SourceRange, UserVal},
function_param::FunctionParam,
};
@ -98,7 +98,16 @@ async fn call_map_closure<'a>(
/// For each item in an array, update a value.
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (array, start, f): (Vec<u64>, Sketch, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let (array, start, f): (Vec<JValue>, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
let array: Vec<KclValue> = array
.into_iter()
.map(|jval| {
KclValue::UserVal(UserVal {
value: jval,
meta: vec![args.source_range.into()],
})
})
.collect();
let reduce_fn = FunctionParam {
inner: f.func,
fn_expr: f.expr,
@ -106,9 +115,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
ctx: args.ctx.clone(),
memory: *f.memory,
};
inner_reduce(array, start, reduce_fn, exec_state, &args)
.await
.map(|sg| KclValue::UserVal(UserVal::new(sg.meta.clone(), sg)))
inner_reduce(array, start, reduce_fn, exec_state, &args).await
}
/// Take a starting value. Then, for each element of an array, calculate the next value,
@ -125,60 +132,52 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// }
/// decagon(5.0) |> close(%)
/// ```
/// ```no_run
/// array = [1, 2, 3]
/// sum = reduce(array, 0, (i, result_so_far) => { return i + result_so_far })
/// assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
/// ```
/// ```no_run
/// fn add = (a, b) => { return a + b }
/// fn sum = (array) => { return reduce(array, 0, add) }
/// assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
/// ```
#[stdlib {
name = "reduce",
}]
async fn inner_reduce<'a>(
array: Vec<u64>,
start: Sketch,
array: Vec<KclValue>,
start: KclValue,
reduce_fn: FunctionParam<'a>,
exec_state: &mut ExecState,
args: &'a Args,
) -> Result<Sketch, KclError> {
) -> Result<KclValue, KclError> {
let mut reduced = start;
for i in array {
reduced = call_reduce_closure(i, reduced, &reduce_fn, args.source_range, exec_state).await?;
for elem in array {
reduced = call_reduce_closure(elem, reduced, &reduce_fn, args.source_range, exec_state).await?;
}
Ok(reduced)
}
async fn call_reduce_closure<'a>(
i: u64,
start: Sketch,
elem: KclValue,
start: KclValue,
reduce_fn: &FunctionParam<'a>,
source_range: SourceRange,
exec_state: &mut ExecState,
) -> Result<Sketch, KclError> {
) -> Result<KclValue, KclError> {
// Call the reduce fn for this repetition.
let reduce_fn_args = vec![
KclValue::UserVal(UserVal {
value: serde_json::Value::Number(i.into()),
meta: vec![source_range.into()],
}),
KclValue::new_user_val(start.meta.clone(), start),
];
let reduce_fn_args = vec![elem, start];
let transform_fn_return = reduce_fn.call(exec_state, reduce_fn_args).await?;
// Unpack the returned transform object.
let source_ranges = vec![source_range];
let closure_retval = transform_fn_return.ok_or_else(|| {
let out = transform_fn_return.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a value".to_string(),
source_ranges: source_ranges.clone(),
})
})?;
let Some(out) = closure_retval.as_user_val() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a UserValue".to_string(),
source_ranges: source_ranges.clone(),
}));
};
let Some((out, _meta)) = out.get() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a Sketch".to_string(),
source_ranges: source_ranges.clone(),
}));
};
Ok(out)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB