KCL: Keyword function calls for stdlib (#4647)
Part of https://github.com/KittyCAD/modeling-app/issues/4600 Adds support for keyword arguments to the stdlib, and calling stdlib functions with keyword arguments. So far, I've changed one function: `rem`. Previously you would have used `rem(7, 2)` but now it's `rem(7, divisor: 2)`. This is a proof-of-concept. If it's approved, we will: 1. Support closures with keyword arguments, and calling them 2. Move the rest of the stdlib to use kw arguments
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -173,6 +173,30 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'CallExpressionKw' && isInRange) {
|
||||||
|
const { callee, arguments: args } = _node
|
||||||
|
if (
|
||||||
|
callee.type === 'Identifier' &&
|
||||||
|
callee.start <= start &&
|
||||||
|
callee.end >= end
|
||||||
|
) {
|
||||||
|
path.push(['callee', 'CallExpressionKw'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (args.length > 0) {
|
||||||
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||||
|
const arg = args[argIndex].arg
|
||||||
|
if (arg.start <= start && arg.end >= end) {
|
||||||
|
path.push(['arguments', 'CallExpressionKw'])
|
||||||
|
path.push([argIndex, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if (_node.type === 'BinaryExpression' && isInRange) {
|
if (_node.type === 'BinaryExpression' && isInRange) {
|
||||||
const { left, right } = _node
|
const { left, right } = _node
|
||||||
if (left.start <= start && left.end >= end) {
|
if (left.start <= start && left.end >= end) {
|
||||||
|
@ -23,17 +23,30 @@ use unbox::unbox;
|
|||||||
struct StdlibMetadata {
|
struct StdlibMetadata {
|
||||||
/// The name of the function in the API.
|
/// The name of the function in the API.
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
/// Tags for the function.
|
/// Tags for the function.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
|
|
||||||
/// Whether the function is unpublished.
|
/// Whether the function is unpublished.
|
||||||
/// Then docs will not be generated.
|
/// Then docs will not be generated.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
unpublished: bool,
|
unpublished: bool,
|
||||||
|
|
||||||
/// Whether the function is deprecated.
|
/// Whether the function is deprecated.
|
||||||
/// Then specific docs detailing that this is deprecated will be generated.
|
/// Then specific docs detailing that this is deprecated will be generated.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
deprecated: bool,
|
deprecated: bool,
|
||||||
|
|
||||||
|
/// If true, expects keyword arguments.
|
||||||
|
/// If false, expects positional arguments.
|
||||||
|
#[serde(default)]
|
||||||
|
keywords: bool,
|
||||||
|
|
||||||
|
/// If true, the first argument is unlabeled.
|
||||||
|
/// If false, all arguments require labels.
|
||||||
|
#[serde(default)]
|
||||||
|
unlabeled_first: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
@ -225,6 +238,12 @@ fn do_stdlib_inner(
|
|||||||
quote! { false }
|
quote! { false }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let uses_keyword_arguments = if metadata.keywords {
|
||||||
|
quote! { true }
|
||||||
|
} else {
|
||||||
|
quote! { false }
|
||||||
|
};
|
||||||
|
|
||||||
let docs_crate = get_crate(None);
|
let docs_crate = get_crate(None);
|
||||||
|
|
||||||
// When the user attaches this proc macro to a function with the wrong type
|
// When the user attaches this proc macro to a function with the wrong type
|
||||||
@ -233,7 +252,7 @@ fn do_stdlib_inner(
|
|||||||
// of the various parameters. We do this by calling dummy functions that
|
// of the various parameters. We do this by calling dummy functions that
|
||||||
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
|
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
|
||||||
let mut arg_types = Vec::new();
|
let mut arg_types = Vec::new();
|
||||||
for arg in ast.sig.inputs.iter() {
|
for (i, arg) in ast.sig.inputs.iter().enumerate() {
|
||||||
// Get the name of the argument.
|
// Get the name of the argument.
|
||||||
let arg_name = match arg {
|
let arg_name = match arg {
|
||||||
syn::FnArg::Receiver(pat) => {
|
syn::FnArg::Receiver(pat) => {
|
||||||
@ -263,7 +282,7 @@ fn do_stdlib_inner(
|
|||||||
|
|
||||||
let ty_string = rust_type_to_openapi_type(&ty_string);
|
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||||
let required = !ty_ident.to_string().starts_with("Option <");
|
let required = !ty_ident.to_string().starts_with("Option <");
|
||||||
|
let label_required = !(i == 0 && metadata.unlabeled_first);
|
||||||
if ty_string != "ExecState" && ty_string != "Args" {
|
if ty_string != "ExecState" && ty_string != "Args" {
|
||||||
let schema = quote! {
|
let schema = quote! {
|
||||||
generator.root_schema_for::<#ty_ident>()
|
generator.root_schema_for::<#ty_ident>()
|
||||||
@ -274,6 +293,7 @@ fn do_stdlib_inner(
|
|||||||
type_: #ty_string.to_string(),
|
type_: #ty_string.to_string(),
|
||||||
schema: #schema,
|
schema: #schema,
|
||||||
required: #required,
|
required: #required,
|
||||||
|
label_required: #label_required,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -334,6 +354,7 @@ fn do_stdlib_inner(
|
|||||||
type_: #ret_ty_string.to_string(),
|
type_: #ret_ty_string.to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -400,6 +421,10 @@ fn do_stdlib_inner(
|
|||||||
vec![#(#tags),*]
|
vec![#(#tags),*]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
#uses_keyword_arguments
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
// We set this to false so we can recurse them later.
|
// We set this to false so we can recurse them later.
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
type_: "Foo".to_string(),
|
type_: "Foo".to_string(),
|
||||||
schema: generator.root_schema_for::<Foo>(),
|
schema: generator.root_schema_for::<Foo>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
type_: "i32".to_string(),
|
type_: "i32".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
type_: "string".to_string(),
|
type_: "string".to_string(),
|
||||||
schema: generator.root_schema_for::<str>(),
|
schema: generator.root_schema_for::<str>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
type_: "i32".to_string(),
|
type_: "i32".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "[number]".to_string(),
|
type_: "[number]".to_string(),
|
||||||
schema: generator.root_schema_for::<[f64; 2usize]>(),
|
schema: generator.root_schema_for::<[f64; 2usize]>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema: generator.root_schema_for::<f64>(),
|
schema: generator.root_schema_for::<f64>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
type_: "kittycad::types::InputFormat".to_string(),
|
type_: "kittycad::types::InputFormat".to_string(),
|
||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
type_: "[Sketch]".to_string(),
|
type_: "[Sketch]".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -118,12 +122,14 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
type_: "LineToData".to_string(),
|
type_: "LineToData".to_string(),
|
||||||
schema: generator.root_schema_for::<LineToData>(),
|
schema: generator.root_schema_for::<LineToData>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
},
|
},
|
||||||
crate::docs::StdLibFnArg {
|
crate::docs::StdLibFnArg {
|
||||||
name: "sketch".to_string(),
|
name: "sketch".to_string(),
|
||||||
type_: "Sketch".to_string(),
|
type_: "Sketch".to_string(),
|
||||||
schema: generator.root_schema_for::<Sketch>(),
|
schema: generator.root_schema_for::<Sketch>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -138,6 +144,7 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
type_: "Sketch".to_string(),
|
type_: "Sketch".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -117,6 +121,7 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
type_: "[number]".to_string(),
|
type_: "[number]".to_string(),
|
||||||
schema: generator.root_schema_for::<Vec<f64>>(),
|
schema: generator.root_schema_for::<Vec<f64>>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +135,7 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema: generator.root_schema_for::<Option<f64>>(),
|
schema: generator.root_schema_for::<Option<f64>>(),
|
||||||
required: false,
|
required: false,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "kittycad::types::InputFormat".to_string(),
|
type_: "kittycad::types::InputFormat".to_string(),
|
||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "number".to_string(),
|
type_: "number".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "kittycad::types::InputFormat".to_string(),
|
type_: "kittycad::types::InputFormat".to_string(),
|
||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "[Sketch]".to_string(),
|
type_: "[Sketch]".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "kittycad::types::InputFormat".to_string(),
|
type_: "kittycad::types::InputFormat".to_string(),
|
||||||
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
|
||||||
required: false,
|
required: false,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
type_: "[Sketch]".to_string(),
|
type_: "[Sketch]".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -83,6 +87,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "[number]".to_string(),
|
type_: "[number]".to_string(),
|
||||||
schema: generator.root_schema_for::<Vec<f64>>(),
|
schema: generator.root_schema_for::<Vec<f64>>(),
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +101,7 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
type_: "()".to_string(),
|
type_: "()".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ impl crate::docs::StdLibFn for SomeFunction {
|
|||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyword_arguments(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
settings.inline_subschemas = inline_subschemas;
|
settings.inline_subschemas = inline_subschemas;
|
||||||
@ -91,6 +95,7 @@ impl crate::docs::StdLibFn for SomeFunction {
|
|||||||
type_: "i32".to_string(),
|
type_: "i32".to_string(),
|
||||||
schema,
|
schema,
|
||||||
required: true,
|
required: true,
|
||||||
|
label_required: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@ pub struct StdLibFnData {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
/// The tags of the function.
|
/// The tags of the function.
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
/// If this function uses keyword arguments, or positional arguments.
|
||||||
|
pub keyword_arguments: bool,
|
||||||
/// The args of the function.
|
/// The args of the function.
|
||||||
pub args: Vec<StdLibFnArg>,
|
pub args: Vec<StdLibFnArg>,
|
||||||
/// The return value of the function.
|
/// The return value of the function.
|
||||||
@ -55,6 +57,18 @@ pub struct StdLibFnArg {
|
|||||||
pub schema: schemars::schema::RootSchema,
|
pub schema: schemars::schema::RootSchema,
|
||||||
/// If the argument is required.
|
/// If the argument is required.
|
||||||
pub required: bool,
|
pub required: bool,
|
||||||
|
/// Even in functions that use keyword arguments, not every parameter requires a label (most do though).
|
||||||
|
/// Some functions allow one unlabeled parameter, which has to be first in the
|
||||||
|
/// argument list.
|
||||||
|
///
|
||||||
|
/// This field is ignored for functions that still use positional arguments.
|
||||||
|
/// Defaults to true.
|
||||||
|
#[serde(default = "its_true")]
|
||||||
|
pub label_required: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn its_true() -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdLibFnArg {
|
impl StdLibFnArg {
|
||||||
@ -120,6 +134,9 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
|||||||
/// The description of the function.
|
/// The description of the function.
|
||||||
fn description(&self) -> String;
|
fn description(&self) -> String;
|
||||||
|
|
||||||
|
/// Does this use keyword arguments, or positional?
|
||||||
|
fn keyword_arguments(&self) -> bool;
|
||||||
|
|
||||||
/// The tags of the function.
|
/// The tags of the function.
|
||||||
fn tags(&self) -> Vec<String>;
|
fn tags(&self) -> Vec<String>;
|
||||||
|
|
||||||
@ -151,6 +168,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
|||||||
summary: self.summary(),
|
summary: self.summary(),
|
||||||
description: self.description(),
|
description: self.description(),
|
||||||
tags: self.tags(),
|
tags: self.tags(),
|
||||||
|
keyword_arguments: self.keyword_arguments(),
|
||||||
args: self.args(false),
|
args: self.args(false),
|
||||||
return_value: self.return_value(false),
|
return_value: self.return_value(false),
|
||||||
unpublished: self.unpublished(),
|
unpublished: self.unpublished(),
|
||||||
@ -806,7 +824,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_function() {
|
fn test_deserialize_function() {
|
||||||
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{},"schemaDefinitions":{}},"args":[],"unpublished":false,"deprecated":false, "examples": []}}"#;
|
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","keywordArguments":false,"summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{},"schemaDefinitions":{}},"args":[],"unpublished":false,"deprecated":false, "examples": []}}"#;
|
||||||
let some_function: crate::parsing::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
let some_function: crate::parsing::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -358,8 +358,47 @@ async fn inner_execute_pipe_body(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Node<CallExpressionKw> {
|
impl Node<CallExpressionKw> {
|
||||||
pub async fn execute(&self, _exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
#[async_recursion]
|
||||||
todo!()
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let fn_name = &self.callee.name;
|
||||||
|
|
||||||
|
// Build a hashmap from argument labels to the final evaluated values.
|
||||||
|
let mut fn_args = HashMap::with_capacity(self.arguments.len());
|
||||||
|
for arg_expr in &self.arguments {
|
||||||
|
let source_range = SourceRange::from(arg_expr.arg.clone());
|
||||||
|
let metadata = Metadata { source_range };
|
||||||
|
let value = ctx
|
||||||
|
.execute_expr(&arg_expr.arg, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
fn_args.insert(arg_expr.label.name.clone(), Arg::new(value, source_range));
|
||||||
|
}
|
||||||
|
let fn_args = fn_args; // remove mutability
|
||||||
|
|
||||||
|
// Evaluate the unlabeled first param, if any exists.
|
||||||
|
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
||||||
|
let source_range = SourceRange::from(arg_expr.clone());
|
||||||
|
let metadata = Metadata { source_range };
|
||||||
|
let value = ctx
|
||||||
|
.execute_expr(arg_expr, exec_state, &metadata, StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
Some(Arg::new(value, source_range))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = crate::std::Args::new_kw(fn_args, unlabeled, self.into(), ctx.clone());
|
||||||
|
match ctx.stdlib.get_either(fn_name) {
|
||||||
|
FunctionKind::Core(func) => {
|
||||||
|
// Attempt to call the function.
|
||||||
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
|
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
FunctionKind::UserDefined => {
|
||||||
|
todo!("Part of modeling-app#4600: Support keyword arguments for user-defined functions")
|
||||||
|
}
|
||||||
|
FunctionKind::Std(_) => todo!("There is no KCL std anymore, it's all core."),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,76 +420,12 @@ impl Node<CallExpression> {
|
|||||||
fn_args.push(arg);
|
fn_args.push(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
match ctx.stdlib.get_either(&self.callee.name) {
|
match ctx.stdlib.get_either(fn_name) {
|
||||||
FunctionKind::Core(func) => {
|
FunctionKind::Core(func) => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||||
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
let mut result = func.std_lib_fn()(exec_state, args).await?;
|
||||||
|
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
||||||
// If the return result is a sketch or solid, we want to update the
|
|
||||||
// memory for the tags of the group.
|
|
||||||
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
|
||||||
// and it works.
|
|
||||||
match result {
|
|
||||||
KclValue::Sketch { value: ref mut sketch } => {
|
|
||||||
for (_, tag) in sketch.tags.iter() {
|
|
||||||
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KclValue::Solid(ref mut solid) => {
|
|
||||||
for value in &solid.value {
|
|
||||||
if let Some(tag) = value.get_tag() {
|
|
||||||
// Get the past tag and update it.
|
|
||||||
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
|
|
||||||
t.clone()
|
|
||||||
} else {
|
|
||||||
// It's probably a fillet or a chamfer.
|
|
||||||
// Initialize it.
|
|
||||||
TagIdentifier {
|
|
||||||
value: tag.name.clone(),
|
|
||||||
info: Some(TagEngineInfo {
|
|
||||||
id: value.get_id(),
|
|
||||||
surface: Some(value.clone()),
|
|
||||||
path: None,
|
|
||||||
sketch: solid.id,
|
|
||||||
}),
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: tag.clone().into(),
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(ref info) = t.info else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Tag {} does not have path info", tag.name),
|
|
||||||
source_ranges: vec![tag.into()],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut info = info.clone();
|
|
||||||
info.surface = Some(value.clone());
|
|
||||||
info.sketch = solid.id;
|
|
||||||
t.info = Some(info);
|
|
||||||
|
|
||||||
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
|
||||||
|
|
||||||
// update the sketch tags.
|
|
||||||
solid.sketch.tags.insert(tag.name.clone(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the stale sketch in memory and update it.
|
|
||||||
if let Some(current_env) = exec_state
|
|
||||||
.memory
|
|
||||||
.environments
|
|
||||||
.get_mut(exec_state.memory.current_env.index())
|
|
||||||
{
|
|
||||||
current_env.update_sketch_tags(&solid.sketch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
FunctionKind::Std(func) => {
|
FunctionKind::Std(func) => {
|
||||||
@ -570,6 +545,73 @@ impl Node<CallExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||||
|
// If the return result is a sketch or solid, we want to update the
|
||||||
|
// memory for the tags of the group.
|
||||||
|
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
||||||
|
// and it works.
|
||||||
|
match result {
|
||||||
|
KclValue::Sketch { value: ref mut sketch } => {
|
||||||
|
for (_, tag) in sketch.tags.iter() {
|
||||||
|
exec_state.memory.update_tag(&tag.value, tag.clone())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KclValue::Solid(ref mut solid) => {
|
||||||
|
for value in &solid.value {
|
||||||
|
if let Some(tag) = value.get_tag() {
|
||||||
|
// Get the past tag and update it.
|
||||||
|
let mut t = if let Some(t) = solid.sketch.tags.get(&tag.name) {
|
||||||
|
t.clone()
|
||||||
|
} else {
|
||||||
|
// It's probably a fillet or a chamfer.
|
||||||
|
// Initialize it.
|
||||||
|
TagIdentifier {
|
||||||
|
value: tag.name.clone(),
|
||||||
|
info: Some(TagEngineInfo {
|
||||||
|
id: value.get_id(),
|
||||||
|
surface: Some(value.clone()),
|
||||||
|
path: None,
|
||||||
|
sketch: solid.id,
|
||||||
|
}),
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: tag.clone().into(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ref info) = t.info else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Tag {} does not have path info", tag.name),
|
||||||
|
source_ranges: vec![tag.into()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut info = info.clone();
|
||||||
|
info.surface = Some(value.clone());
|
||||||
|
info.sketch = solid.id;
|
||||||
|
t.info = Some(info);
|
||||||
|
|
||||||
|
exec_state.memory.update_tag(&tag.name, t.clone())?;
|
||||||
|
|
||||||
|
// update the sketch tags.
|
||||||
|
solid.sketch.tags.insert(tag.name.clone(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the stale sketch in memory and update it.
|
||||||
|
if let Some(current_env) = exec_state
|
||||||
|
.memory
|
||||||
|
.environments
|
||||||
|
.get_mut(exec_state.memory.current_env.index())
|
||||||
|
{
|
||||||
|
current_env.update_sketch_tags(&solid.sketch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl Node<TagDeclarator> {
|
impl Node<TagDeclarator> {
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||||
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{any::type_name, num::NonZeroU32};
|
use std::{any::type_name, collections::HashMap, num::NonZeroU32};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
|
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
|
||||||
@ -45,7 +45,12 @@ impl Arg {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
|
/// Positional args.
|
||||||
pub args: Vec<Arg>,
|
pub args: Vec<Arg>,
|
||||||
|
/// Keyword args.
|
||||||
|
pub kw_args: HashMap<String, Arg>,
|
||||||
|
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
|
||||||
|
pub unlabeled_kw_arg: Option<Arg>,
|
||||||
pub source_range: SourceRange,
|
pub source_range: SourceRange,
|
||||||
pub ctx: ExecutorContext,
|
pub ctx: ExecutorContext,
|
||||||
}
|
}
|
||||||
@ -54,6 +59,24 @@ impl Args {
|
|||||||
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
|
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
args,
|
args,
|
||||||
|
kw_args: Default::default(),
|
||||||
|
unlabeled_kw_arg: Default::default(),
|
||||||
|
source_range,
|
||||||
|
ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect the given keyword arguments.
|
||||||
|
pub fn new_kw(
|
||||||
|
kw_args: HashMap<String, Arg>,
|
||||||
|
unlabeled_kw_arg: Option<Arg>,
|
||||||
|
source_range: SourceRange,
|
||||||
|
ctx: ExecutorContext,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
args: Default::default(),
|
||||||
|
kw_args,
|
||||||
|
unlabeled_kw_arg,
|
||||||
source_range,
|
source_range,
|
||||||
ctx,
|
ctx,
|
||||||
}
|
}
|
||||||
@ -65,6 +88,8 @@ impl Args {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
args: Vec::new(),
|
args: Vec::new(),
|
||||||
|
kw_args: Default::default(),
|
||||||
|
unlabeled_kw_arg: Default::default(),
|
||||||
source_range: SourceRange::default(),
|
source_range: SourceRange::default(),
|
||||||
ctx: ExecutorContext {
|
ctx: ExecutorContext {
|
||||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
||||||
@ -76,6 +101,50 @@ impl Args {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a keyword argument. If not set, returns None.
|
||||||
|
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Option<T>
|
||||||
|
where
|
||||||
|
T: FromKclValue<'a>,
|
||||||
|
{
|
||||||
|
self.kw_args.get(label).and_then(|arg| T::from_kcl_val(&arg.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a keyword argument. If not set, returns Err.
|
||||||
|
pub(crate) fn get_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
||||||
|
where
|
||||||
|
T: FromKclValue<'a>,
|
||||||
|
{
|
||||||
|
self.get_kw_arg_opt(label).ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
message: format!("This function requires a keyword argument '{label}'"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the unlabeled keyword argument. If not set, returns Err.
|
||||||
|
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
||||||
|
where
|
||||||
|
T: FromKclValue<'a>,
|
||||||
|
{
|
||||||
|
let Some(ref arg) = self.unlabeled_kw_arg else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
T::from_kcl_val(&arg.value).ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: arg.source_ranges(),
|
||||||
|
message: format!(
|
||||||
|
"Expected a {} but found {}",
|
||||||
|
type_name::<T>(),
|
||||||
|
arg.value.human_friendly_type()
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Add a modeling command to the batch but don't fire it right away.
|
// Add a modeling command to the batch but don't fire it right away.
|
||||||
pub(crate) async fn batch_modeling_cmd(
|
pub(crate) async fn batch_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
|
|
||||||
use super::args::FromArgs;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{ExecState, KclValue},
|
executor::{ExecState, KclValue},
|
||||||
@ -13,7 +12,8 @@ use crate::{
|
|||||||
/// Compute the remainder after dividing `num` by `div`.
|
/// Compute the remainder after dividing `num` by `div`.
|
||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let (n, d) = FromArgs::from_args(&args, 0)?;
|
let n = args.get_unlabeled_kw_arg("number to divide")?;
|
||||||
|
let d = args.get_kw_arg("divisor")?;
|
||||||
let result = inner_rem(n, d)?;
|
let result = inner_rem(n, d)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_i64(result))
|
Ok(args.make_user_val_from_i64(result))
|
||||||
@ -23,13 +23,15 @@ pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// assertEqual(rem(7, 4), 3, 0.01, "remainder is 3")
|
/// assertEqual(rem(7, divisor: 4), 3, 0.01, "remainder is 3")
|
||||||
/// assertEqual(rem(-7, 4), -3, 0.01, "remainder is 3")
|
/// assertEqual(rem(-7, divisor: 4), -3, 0.01, "remainder is 3")
|
||||||
/// assertEqual(rem(7, -4), 3, 0.01, "remainder is 3")
|
/// assertEqual(rem(7, divisor: -4), 3, 0.01, "remainder is 3")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "rem",
|
name = "rem",
|
||||||
tags = ["math"],
|
tags = ["math"],
|
||||||
|
keywords = true,
|
||||||
|
unlabeled_first = true,
|
||||||
}]
|
}]
|
||||||
fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> {
|
fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> {
|
||||||
Ok(num % divisor)
|
Ok(num % divisor)
|
||||||
|
Reference in New Issue
Block a user