KCL stdlib 'map' function (#4054)
I had to revert https://github.com/KittyCAD/modeling-app/pull/4031 because it broke syntax highlighting. This is the same PR, but updated to fix syntax highlighting. Highlighting broke because the KCL LSP could not determine how to autocomplete the `map` function. The first argument of `map` is `[KclValue]` and the LSP doesn't know any good suggestions for "any KCL value", so it error'd out. I am using the value `[0..9]` for this case now. Tested that syntax highlighting works again.
This commit is contained in:
@ -81,6 +81,8 @@ impl StdLibFnArg {
|
||||
} else if self.type_ == "TagIdentifier" && self.required {
|
||||
// TODO: actually use the ast to populate this.
|
||||
return Ok(Some((index, format!("${{{}:{}}}", index, "myTag"))));
|
||||
} else if self.type_ == "[KclValue]" && self.required {
|
||||
return Ok(Some((index, "[0..9]".to_owned())));
|
||||
}
|
||||
get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index)
|
||||
}
|
||||
@ -903,6 +905,12 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_map() {
|
||||
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
|
||||
let _snippet = map_fn.to_autocomplete_snippet().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_pattern_linear_2d() {
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
||||
|
||||
@ -4,13 +4,14 @@ use anyhow::Result;
|
||||
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JValue;
|
||||
|
||||
use crate::{
|
||||
ast::types::{parse_json_number_as_f64, TagDeclarator},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{
|
||||
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
|
||||
SolidSet, SourceRange, TagIdentifier,
|
||||
SolidSet, SourceRange, TagIdentifier, UserVal,
|
||||
},
|
||||
std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg},
|
||||
};
|
||||
@ -497,18 +498,6 @@ 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,
|
||||
@ -587,6 +576,20 @@ impl<'a> FromKclValue<'a> for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for UserVal {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.as_user_val().map(|x| x.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<JValue> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.as_user_val()
|
||||
.and_then(|uv| uv.value.as_array())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for TagDeclarator {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.get_tag_declarator().ok()
|
||||
@ -599,6 +602,12 @@ impl<'a> FromKclValue<'a> for TagIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for KclValue {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
Some(arg.clone())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arg_via_json {
|
||||
($typ:path) => {
|
||||
impl<'a> FromKclValue<'a> for $typ {
|
||||
@ -609,6 +618,15 @@ macro_rules! impl_from_arg_via_json {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, T> FromKclValue<'a> for Vec<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned + FromKclValue<'a>,
|
||||
{
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
from_user_val(arg)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arg_for_array {
|
||||
($n:literal) => {
|
||||
impl<'a, T> FromKclValue<'a> for [T; $n]
|
||||
@ -722,23 +740,3 @@ impl<'a> FromKclValue<'a> for SketchSurface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<Sketch> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::UserVal(uv) = arg else {
|
||||
return None;
|
||||
};
|
||||
|
||||
uv.get::<Vec<Sketch>>().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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use derive_docs::stdlib;
|
||||
use serde_json::Value as JValue;
|
||||
|
||||
use super::{args::FromArgs, Args, FnAsArg};
|
||||
use crate::{
|
||||
@ -7,6 +8,94 @@ use crate::{
|
||||
function_param::FunctionParam,
|
||||
};
|
||||
|
||||
/// Apply a function to each element of an array.
|
||||
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (array, f): (Vec<JValue>, 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 map_fn = FunctionParam {
|
||||
inner: f.func,
|
||||
fn_expr: f.expr,
|
||||
meta: vec![args.source_range.into()],
|
||||
ctx: args.ctx.clone(),
|
||||
memory: *f.memory,
|
||||
};
|
||||
let new_array = inner_map(array, map_fn, exec_state, &args).await?;
|
||||
let uv = UserVal::new(vec![args.source_range.into()], new_array);
|
||||
Ok(KclValue::UserVal(uv))
|
||||
}
|
||||
|
||||
/// Apply a function to every element of a list.
|
||||
///
|
||||
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
||||
/// `[f(a), f(b), f(c)]`
|
||||
/// ```no_run
|
||||
/// const r = 10 // radius
|
||||
/// fn drawCircle = (id) => {
|
||||
/// return startSketchOn("XY")
|
||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
||||
/// }
|
||||
///
|
||||
/// // Call `drawCircle`, passing in each element of the array.
|
||||
/// // The outputs from each `drawCircle` form a new array,
|
||||
/// // which is the return value from `map`.
|
||||
/// const circles = map(
|
||||
/// [1..3],
|
||||
/// drawCircle
|
||||
/// )
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// const r = 10 // radius
|
||||
/// // Call `map`, using an anonymous function instead of a named one.
|
||||
/// const circles = map(
|
||||
/// [1..3],
|
||||
/// (id) => {
|
||||
/// return startSketchOn("XY")
|
||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "map",
|
||||
}]
|
||||
async fn inner_map<'a>(
|
||||
array: Vec<KclValue>,
|
||||
map_fn: FunctionParam<'a>,
|
||||
exec_state: &mut ExecState,
|
||||
args: &'a Args,
|
||||
) -> Result<Vec<KclValue>, KclError> {
|
||||
let mut new_array = Vec::with_capacity(array.len());
|
||||
for elem in array {
|
||||
let new_elem = call_map_closure(elem, &map_fn, args.source_range, exec_state).await?;
|
||||
new_array.push(new_elem);
|
||||
}
|
||||
Ok(new_array)
|
||||
}
|
||||
|
||||
async fn call_map_closure<'a>(
|
||||
input: KclValue,
|
||||
map_fn: &FunctionParam<'a>,
|
||||
source_range: SourceRange,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<KclValue, KclError> {
|
||||
let output = map_fn.call(exec_state, vec![input]).await?;
|
||||
let source_ranges = vec![source_range];
|
||||
let output = output.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: "Map function must return a value".to_string(),
|
||||
source_ranges,
|
||||
})
|
||||
})?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
||||
@ -98,6 +98,7 @@ lazy_static! {
|
||||
Box::new(crate::std::patterns::PatternCircular3D),
|
||||
Box::new(crate::std::patterns::PatternTransform),
|
||||
Box::new(crate::std::array::Reduce),
|
||||
Box::new(crate::std::array::Map),
|
||||
Box::new(crate::std::chamfer::Chamfer),
|
||||
Box::new(crate::std::fillet::Fillet),
|
||||
Box::new(crate::std::fillet::GetOppositeEdge),
|
||||
|
||||
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_map0.png
Normal file
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_map0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_map1.png
Normal file
BIN
src/wasm-lib/kcl/tests/outputs/serial_test_example_map1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Reference in New Issue
Block a user