KCL stdlib reduce function (#3881)
Adds an `arrayReduce` function to KCL stdlib. Right now, it can only reduce SketchGroup values because my implementation of higher-order KCL functions sucks. But we will generalize it in the future to be able to reduce any type. This simplifies sketching polygons, e.g. ``` fn decagon = (radius) => { let step = (1/10) * tau() let sketch = startSketchAt([ (cos(0) * radius), (sin(0) * radius), ]) return arrayReduce([1..10], sketch, (i, sg) => { let x = cos(step * i) * radius let y = sin(step * i) * radius return lineTo([x, y], sg) }) } ``` Part of #3842
This commit is contained in:
858
docs/kcl/arrayReduce.md
Normal file
858
docs/kcl/arrayReduce.md
Normal file
File diff suppressed because one or more lines are too long
@ -19,6 +19,7 @@ layout: manual
|
||||
* [`angledLineToX`](kcl/angledLineToX)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arrayReduce`](kcl/arrayReduce)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
|
6670
docs/kcl/std.json
6670
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
@ -548,7 +548,7 @@ fn array_end_start(i: TokenSlice) -> PResult<ArrayExpression> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse n..m into a vec of numbers [n, n+1, ..., m]
|
||||
/// Parse n..m into a vec of numbers [n, n+1, ..., m-1]
|
||||
fn integer_range(i: TokenSlice) -> PResult<Vec<Expr>> {
|
||||
let (token0, floor) = integer.parse_next(i)?;
|
||||
double_period.parse_next(i)?;
|
||||
|
@ -503,6 +503,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromArgs<'a> for KclValue {
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(v) = args.args.get(i) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Argument at index {i} was missing",),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
Ok(v.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromArgs<'a> for Option<T>
|
||||
where
|
||||
T: FromKclValue<'a> + Sized,
|
||||
@ -716,3 +728,13 @@ impl<'a> FromKclValue<'a> for Vec<SketchGroup> {
|
||||
uv.get::<Vec<SketchGroup>>().map(|x| x.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<u64> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::UserVal(uv) = arg else {
|
||||
return None;
|
||||
};
|
||||
|
||||
uv.get::<Vec<u64>>().map(|x| x.0)
|
||||
}
|
||||
}
|
||||
|
96
src/wasm-lib/kcl/src/std/array.rs
Normal file
96
src/wasm-lib/kcl/src/std/array.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{KclValue, SketchGroup, SourceRange, UserVal},
|
||||
function_param::FunctionParam,
|
||||
};
|
||||
|
||||
use super::{args::FromArgs, Args, FnAsArg};
|
||||
|
||||
/// For each item in an array, update a value.
|
||||
pub async fn array_reduce(args: Args) -> Result<KclValue, KclError> {
|
||||
let (array, start, f): (Vec<u64>, SketchGroup, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
|
||||
let reduce_fn = FunctionParam {
|
||||
inner: f.func,
|
||||
fn_expr: f.expr,
|
||||
meta: vec![args.source_range.into()],
|
||||
ctx: args.ctx.clone(),
|
||||
memory: *f.memory,
|
||||
dynamic_state: args.dynamic_state.clone(),
|
||||
};
|
||||
inner_array_reduce(array, start, reduce_fn, &args)
|
||||
.await
|
||||
.map(|sg| KclValue::UserVal(UserVal::set(sg.meta.clone(), sg)))
|
||||
}
|
||||
|
||||
/// Take a starting value. Then, for each element of an array, calculate the next value,
|
||||
/// using the previous value and the element.
|
||||
/// ```no_run
|
||||
/// fn decagon = (radius) => {
|
||||
/// let step = (1/10) * tau()
|
||||
/// let sketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||
/// return arrayReduce([1..10], sketch, (i, sg) => {
|
||||
/// let x = cos(step * i) * radius
|
||||
/// let y = sin(step * i) * radius
|
||||
/// return lineTo([x, y], sg)
|
||||
/// })
|
||||
/// }
|
||||
/// decagon(5.0) |> close(%)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arrayReduce",
|
||||
}]
|
||||
async fn inner_array_reduce<'a>(
|
||||
array: Vec<u64>,
|
||||
start: SketchGroup,
|
||||
reduce_fn: FunctionParam<'a>,
|
||||
args: &'a Args,
|
||||
) -> Result<SketchGroup, KclError> {
|
||||
let mut reduced = start;
|
||||
for i in array {
|
||||
reduced = call_reduce_closure(i, reduced, &reduce_fn, args.source_range).await?;
|
||||
}
|
||||
|
||||
Ok(reduced)
|
||||
}
|
||||
|
||||
async fn call_reduce_closure<'a>(
|
||||
i: u64,
|
||||
start: SketchGroup,
|
||||
reduce_fn: &FunctionParam<'a>,
|
||||
source_range: SourceRange,
|
||||
) -> Result<SketchGroup, 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 transform_fn_return = reduce_fn.call(reduce_fn_args).await?;
|
||||
|
||||
// Unpack the returned transform object.
|
||||
let source_ranges = vec![source_range];
|
||||
let closure_retval = 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 SketchGroup".to_string(),
|
||||
source_ranges: source_ranges.clone(),
|
||||
}));
|
||||
};
|
||||
Ok(out)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
//! Functions implemented for language execution.
|
||||
|
||||
pub mod args;
|
||||
pub mod array;
|
||||
pub mod assert;
|
||||
pub mod chamfer;
|
||||
pub mod convert;
|
||||
@ -91,6 +92,7 @@ lazy_static! {
|
||||
Box::new(crate::std::patterns::PatternCircular2D),
|
||||
Box::new(crate::std::patterns::PatternCircular3D),
|
||||
Box::new(crate::std::patterns::PatternTransform),
|
||||
Box::new(crate::std::array::ArrayReduce),
|
||||
Box::new(crate::std::chamfer::Chamfer),
|
||||
Box::new(crate::std::fillet::Fillet),
|
||||
Box::new(crate::std::fillet::GetOppositeEdge),
|
||||
|
@ -134,7 +134,7 @@ async fn inner_pattern_transform<'a>(
|
||||
args: &'a Args,
|
||||
) -> Result<Vec<Box<ExtrudeGroup>>, KclError> {
|
||||
// Build the vec of transforms, one for each repetition.
|
||||
let mut transform = Vec::new();
|
||||
let mut transform = Vec::with_capacity(usize::try_from(num_repetitions).unwrap());
|
||||
for i in 0..num_repetitions {
|
||||
let t = make_transform(i, &transform_function, args.source_range).await?;
|
||||
transform.push(t);
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Reference in New Issue
Block a user