Remove just one enum (#1096)
# Problem This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib. Previously, if users want to put a tag on the arc, the function's parameters change type. ``` // Tag missing: first param is array tangentialArcTo([x, y], %) // Tag present: first param is object tangentialArcTo({to: [x, y], tag: "myTag"}, %) ``` # Solution My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like ``` // Tag missing: first param is array tangentialArcTo([x, y], %) // Tag present: first param is array still, but we now pass a tag at the end. tangentialArcTo([x, y], %, "myTag") ``` This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
This commit is contained in:
@ -19765,25 +19765,9 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "data",
|
"name": "to",
|
||||||
"type": "TangentialArcToData",
|
"type": "[number]",
|
||||||
"schema": {
|
"schema": {
|
||||||
"description": "Data to draw a tangential arc to a specific point.",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"description": "A point with a tag.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"tag",
|
|
||||||
"to"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"tag": {
|
|
||||||
"description": "The tag.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"to": {
|
|
||||||
"description": "Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@ -19791,20 +19775,6 @@
|
|||||||
},
|
},
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"minItems": 2
|
"minItems": 2
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
},
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
@ -20246,6 +20216,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tag",
|
||||||
|
"type": "String",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returnValue": {
|
"returnValue": {
|
||||||
|
@ -3905,21 +3905,12 @@ Draw an arc.
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
tangentialArcTo(data: TangentialArcToData, sketch_group: SketchGroup) -> SketchGroup
|
tangentialArcTo(to: [number], sketch_group: SketchGroup, tag: String) -> SketchGroup
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
||||||
* `data`: `TangentialArcToData` - Data to draw a tangential arc to a specific point.
|
* `to`: `[number]`
|
||||||
```
|
|
||||||
{
|
|
||||||
// The tag.
|
|
||||||
tag: string,
|
|
||||||
// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
|
|
||||||
to: [number, number],
|
|
||||||
} |
|
|
||||||
[number, number]
|
|
||||||
```
|
|
||||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
@ -3985,6 +3976,7 @@ tangentialArcTo(data: TangentialArcToData, sketch_group: SketchGroup) -> SketchG
|
|||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
* `tag`: `String`
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
|
@ -1537,7 +1537,8 @@ export function isLiteralArrayOrStatic(
|
|||||||
if (!val) return false
|
if (!val) return false
|
||||||
|
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
const [a, b] = val
|
const a = val[0]
|
||||||
|
const b = val[1]
|
||||||
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -1550,7 +1551,8 @@ export function isNotLiteralArrayOrStatic(
|
|||||||
val: Value | [Value, Value] | [Value, Value, Value]
|
val: Value | [Value, Value] | [Value, Value, Value]
|
||||||
): boolean {
|
): boolean {
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
const [a, b] = val
|
const a = val[0]
|
||||||
|
const b = val[1]
|
||||||
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
18
src/wasm-lib/Cargo.lock
generated
18
src/wasm-lib/Cargo.lock
generated
@ -719,24 +719,12 @@ version = "0.1.4"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
|
"once_cell",
|
||||||
"openapitor",
|
"openapitor",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"regex",
|
||||||
"serde_tokenstream",
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive-docs"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c357dec14992ba88803535217ed83d6f6cd80efcb8fa8e3f8a30a9b84fadc1c7"
|
|
||||||
dependencies = [
|
|
||||||
"convert_case",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.39",
|
"syn 2.0.39",
|
||||||
@ -1436,7 +1424,7 @@ dependencies = [
|
|||||||
"criterion",
|
"criterion",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"databake",
|
"databake",
|
||||||
"derive-docs 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"derive-docs",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
"futures",
|
"futures",
|
||||||
"insta",
|
"insta",
|
||||||
|
@ -14,8 +14,10 @@ proc-macro = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
|
once_cell = "1.18.0"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
regex = "1.10"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.39", features = ["full"] }
|
syn = { version = "2.0.39", features = ["full"] }
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
// Copyright 2023 Oxide Computer Company
|
|
||||||
|
|
||||||
//! This package defines macro attributes associated with HTTP handlers. These
|
|
||||||
//! attributes are used both to define an HTTP API and to generate an OpenAPI
|
|
||||||
//! Spec (OAS) v3 document that describes the API.
|
|
||||||
|
|
||||||
// Clippy's style advice is definitely valuable, but not worth the trouble for
|
// Clippy's style advice is definitely valuable, but not worth the trouble for
|
||||||
// automated enforcement.
|
// automated enforcement.
|
||||||
#![allow(clippy::style)]
|
#![allow(clippy::style)]
|
||||||
|
|
||||||
use convert_case::Casing;
|
use convert_case::Casing;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||||
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_tokenstream::{from_tokenstream, Error};
|
use serde_tokenstream::{from_tokenstream, Error};
|
||||||
use syn::{
|
use syn::{
|
||||||
@ -209,6 +205,18 @@ fn do_stdlib_inner(
|
|||||||
quote! {
|
quote! {
|
||||||
Vec<#ty_ident>
|
Vec<#ty_ident>
|
||||||
}
|
}
|
||||||
|
} else if ty_string.starts_with("Option<") {
|
||||||
|
let ty_string = ty_string.trim_start_matches("Option<").trim_end_matches('>');
|
||||||
|
let ty_ident = format_ident!("{}", ty_string);
|
||||||
|
quote! {
|
||||||
|
Option<#ty_ident>
|
||||||
|
}
|
||||||
|
} else if let Some((inner_array_type, num)) = parse_array_type(&ty_string) {
|
||||||
|
let ty_string = inner_array_type.to_owned();
|
||||||
|
let ty_ident = format_ident!("{}", ty_string);
|
||||||
|
quote! {
|
||||||
|
[#ty_ident; #num]
|
||||||
|
}
|
||||||
} else if ty_string.starts_with("Box<") {
|
} else if ty_string.starts_with("Box<") {
|
||||||
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
||||||
let ty_ident = format_ident!("{}", ty_string);
|
let ty_ident = format_ident!("{}", ty_string);
|
||||||
@ -222,10 +230,13 @@ fn do_stdlib_inner(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ty_string = clean_type(&ty_string);
|
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||||
|
|
||||||
if ty_string != "Args" {
|
if ty_string != "Args" {
|
||||||
let schema = if ty_ident.to_string().starts_with("Vec < ") {
|
let schema = if ty_ident.to_string().starts_with("Vec < ")
|
||||||
|
|| ty_ident.to_string().starts_with("Option <")
|
||||||
|
|| ty_ident.to_string().starts_with('[')
|
||||||
|
{
|
||||||
quote! {
|
quote! {
|
||||||
<#ty_ident>::json_schema(&mut generator)
|
<#ty_ident>::json_schema(&mut generator)
|
||||||
}
|
}
|
||||||
@ -263,7 +274,7 @@ fn do_stdlib_inner(
|
|||||||
ret_ty_string.trim().to_string()
|
ret_ty_string.trim().to_string()
|
||||||
};
|
};
|
||||||
let ret_ty_ident = format_ident!("{}", ret_ty_string);
|
let ret_ty_ident = format_ident!("{}", ret_ty_string);
|
||||||
let ret_ty_string = clean_type(&ret_ty_string);
|
let ret_ty_string = rust_type_to_openapi_type(&ret_ty_string);
|
||||||
quote! {
|
quote! {
|
||||||
Some(#docs_crate::StdLibFnArg {
|
Some(#docs_crate::StdLibFnArg {
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
@ -488,15 +499,22 @@ impl Parse for ItemFnForSignature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_type(t: &str) -> String {
|
fn rust_type_to_openapi_type(t: &str) -> String {
|
||||||
let mut t = t.to_string();
|
let mut t = t.to_string();
|
||||||
// Turn vecs into arrays.
|
// Turn vecs into arrays.
|
||||||
|
// TODO: handle nested types
|
||||||
if t.starts_with("Vec<") {
|
if t.starts_with("Vec<") {
|
||||||
t = t.replace("Vec<", "[").replace('>', "]");
|
t = t.replace("Vec<", "[").replace('>', "]");
|
||||||
}
|
}
|
||||||
if t.starts_with("Box<") {
|
if t.starts_with("Box<") {
|
||||||
t = t.replace("Box<", "").replace('>', "");
|
t = t.replace("Box<", "").replace('>', "");
|
||||||
}
|
}
|
||||||
|
if t.starts_with("Option<") {
|
||||||
|
t = t.replace("Option<", "").replace('>', "");
|
||||||
|
}
|
||||||
|
if let Some((inner_type, _length)) = parse_array_type(&t) {
|
||||||
|
t = format!("[{inner_type}]")
|
||||||
|
}
|
||||||
|
|
||||||
if t == "f64" {
|
if t == "f64" {
|
||||||
return "number".to_string();
|
return "number".to_string();
|
||||||
@ -507,6 +525,14 @@ fn clean_type(t: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_array_type(type_name: &str) -> Option<(&str, usize)> {
|
||||||
|
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[([a-zA-Z0-9<>]+); ?(\d+)\]").unwrap());
|
||||||
|
let cap = RE.captures(type_name)?;
|
||||||
|
let inner_type = cap.get(1)?;
|
||||||
|
let length = cap.get(2)?.as_str().parse().ok()?;
|
||||||
|
Some((inner_type.as_str(), length))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
@ -514,6 +540,19 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_inner_array_type() {
|
||||||
|
for (expected, input) in [
|
||||||
|
(Some(("f64", 2)), "[f64;2]"),
|
||||||
|
(Some(("String", 2)), "[String; 2]"),
|
||||||
|
(Some(("Option<String>", 12)), "[Option<String>;12]"),
|
||||||
|
(Some(("Option<String>", 12)), "[Option<String>; 12]"),
|
||||||
|
] {
|
||||||
|
let actual = parse_array_type(input);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stdlib_line_to() {
|
fn test_stdlib_line_to() {
|
||||||
let (item, errors) = do_stdlib(
|
let (item, errors) = do_stdlib(
|
||||||
@ -608,4 +647,46 @@ mod tests {
|
|||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdlib_option() {
|
||||||
|
let (item, errors) = do_stdlib(
|
||||||
|
quote! {
|
||||||
|
name = "show",
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
fn inner_show(
|
||||||
|
/// The args to do shit to.
|
||||||
|
args: Option<f64>
|
||||||
|
) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
expectorate::assert_contents("tests/option.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdlib_array() {
|
||||||
|
let (item, errors) = do_stdlib(
|
||||||
|
quote! {
|
||||||
|
name = "show",
|
||||||
|
},
|
||||||
|
quote! {
|
||||||
|
fn inner_show(
|
||||||
|
/// The args to do shit to.
|
||||||
|
args: [f64; 2]
|
||||||
|
) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
expectorate::assert_contents("tests/array.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
82
src/wasm-lib/derive-docs/tests/array.gen
Normal file
82
src/wasm-lib/derive-docs/tests/array.gen
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub(crate) struct Show {}
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
pub(crate) const Show: Show = Show {};
|
||||||
|
fn boxed_show(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<
|
||||||
|
dyn std::future::Future<
|
||||||
|
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
Box::pin(show(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::docs::StdLibFn for Show {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"show".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
vec![crate::docs::StdLibFnArg {
|
||||||
|
name: "args".to_string(),
|
||||||
|
type_: "[number]".to_string(),
|
||||||
|
schema: <[f64; 2usize]>::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
Some(crate::docs::StdLibFnArg {
|
||||||
|
name: "".to_string(),
|
||||||
|
type_: "number".to_string(),
|
||||||
|
schema: f64::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublished(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deprecated(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
|
boxed_show
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_show(#[doc = r" The args to do shit to."] args: [f64; 2]) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
82
src/wasm-lib/derive-docs/tests/option.gen
Normal file
82
src/wasm-lib/derive-docs/tests/option.gen
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub(crate) struct Show {}
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals, missing_docs)]
|
||||||
|
#[doc = "Std lib function: show"]
|
||||||
|
pub(crate) const Show: Show = Show {};
|
||||||
|
fn boxed_show(
|
||||||
|
args: crate::std::Args,
|
||||||
|
) -> std::pin::Pin<
|
||||||
|
Box<
|
||||||
|
dyn std::future::Future<
|
||||||
|
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
Box::pin(show(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::docs::StdLibFn for Show {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"show".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tags(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
vec![crate::docs::StdLibFnArg {
|
||||||
|
name: "args".to_string(),
|
||||||
|
type_: "number".to_string(),
|
||||||
|
schema: <Option<f64>>::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||||
|
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||||
|
Some(crate::docs::StdLibFnArg {
|
||||||
|
name: "".to_string(),
|
||||||
|
type_: "number".to_string(),
|
||||||
|
schema: f64::json_schema(&mut generator),
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpublished(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deprecated(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||||
|
boxed_show
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_show(#[doc = r" The args to do shit to."] args: Option<f64>) -> Box<f64> {
|
||||||
|
args
|
||||||
|
}
|
@ -18,8 +18,8 @@ async-trait = "0.1.73"
|
|||||||
clap = { version = "4.4.8", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
clap = { version = "4.4.8", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
databake = { version = "0.1.7", features = ["derive"] }
|
databake = { version = "0.1.7", features = ["derive"] }
|
||||||
derive-docs = { version = "0.1.4" }
|
# derive-docs = { version = "0.1.4" }
|
||||||
#derive-docs = { path = "../derive-docs" }
|
derive-docs = { path = "../derive-docs" }
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
parse-display = "0.8.2"
|
parse-display = "0.8.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Data types for the AST.
|
//! Data types for the AST.
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt::Write};
|
use std::{collections::HashMap, fmt::Write, ops::RangeInclusive};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use databake::*;
|
use databake::*;
|
||||||
@ -11,6 +11,7 @@ use serde_json::{Map, Value as JValue};
|
|||||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
||||||
|
|
||||||
pub use self::literal_value::LiteralValue;
|
pub use self::literal_value::LiteralValue;
|
||||||
|
pub use self::none::KclNone;
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
@ -20,6 +21,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod literal_value;
|
mod literal_value;
|
||||||
|
mod none;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||||
#[databake(path = kcl_lib::ast::types)]
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
@ -407,6 +409,7 @@ pub enum Value {
|
|||||||
ObjectExpression(Box<ObjectExpression>),
|
ObjectExpression(Box<ObjectExpression>),
|
||||||
MemberExpression(Box<MemberExpression>),
|
MemberExpression(Box<MemberExpression>),
|
||||||
UnaryExpression(Box<UnaryExpression>),
|
UnaryExpression(Box<UnaryExpression>),
|
||||||
|
None(KclNone),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
@ -423,6 +426,9 @@ impl Value {
|
|||||||
Value::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
Value::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
||||||
Value::UnaryExpression(unary_exp) => unary_exp.recast(options),
|
Value::UnaryExpression(unary_exp) => unary_exp.recast(options),
|
||||||
Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
|
Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
|
||||||
|
Value::None(_) => {
|
||||||
|
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,6 +450,7 @@ impl Value {
|
|||||||
Value::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
|
Value::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
|
||||||
Value::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
|
Value::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
|
||||||
Value::PipeSubstitution(_) => {}
|
Value::PipeSubstitution(_) => {}
|
||||||
|
Value::None(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,6 +467,7 @@ impl Value {
|
|||||||
Value::ObjectExpression(object_expression) => object_expression.start(),
|
Value::ObjectExpression(object_expression) => object_expression.start(),
|
||||||
Value::MemberExpression(member_expression) => member_expression.start(),
|
Value::MemberExpression(member_expression) => member_expression.start(),
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.start(),
|
Value::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||||
|
Value::None(none) => none.start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,6 +484,7 @@ impl Value {
|
|||||||
Value::ObjectExpression(object_expression) => object_expression.end(),
|
Value::ObjectExpression(object_expression) => object_expression.end(),
|
||||||
Value::MemberExpression(member_expression) => member_expression.end(),
|
Value::MemberExpression(member_expression) => member_expression.end(),
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.end(),
|
Value::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||||
|
Value::None(none) => none.end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,19 +492,22 @@ impl Value {
|
|||||||
/// This is really recursive so keep that in mind.
|
/// This is really recursive so keep that in mind.
|
||||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||||
match self {
|
match self {
|
||||||
Value::Literal(_literal) => None,
|
|
||||||
Value::Identifier(_identifier) => None,
|
|
||||||
Value::BinaryExpression(binary_expression) => binary_expression.get_hover_value_for_position(pos, code),
|
Value::BinaryExpression(binary_expression) => binary_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
function_expression.get_hover_value_for_position(pos, code)
|
function_expression.get_hover_value_for_position(pos, code)
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
Value::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code),
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::PipeSubstitution(_) => None,
|
|
||||||
Value::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code),
|
Value::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
Value::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
Value::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
Value::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||||
|
// TODO: LSP hover information for values/types. https://github.com/KittyCAD/modeling-app/issues/1126
|
||||||
|
Value::None(_) => None,
|
||||||
|
Value::Literal(_) => None,
|
||||||
|
Value::Identifier(_) => None,
|
||||||
|
// TODO: LSP hover information for symbols. https://github.com/KittyCAD/modeling-app/issues/1127
|
||||||
|
Value::PipeSubstitution(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +531,7 @@ impl Value {
|
|||||||
member_expression.rename_identifiers(old_name, new_name)
|
member_expression.rename_identifiers(old_name, new_name)
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
||||||
|
Value::None(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,6 +552,7 @@ impl Value {
|
|||||||
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||||
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||||
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||||
|
Value::None(none) => none.get_constraint_level(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -926,6 +940,7 @@ impl CallExpression {
|
|||||||
|
|
||||||
for arg in &self.arguments {
|
for arg in &self.arguments {
|
||||||
let result: MemoryItem = match arg {
|
let result: MemoryItem = match arg {
|
||||||
|
Value::None(none) => none.into(),
|
||||||
Value::Literal(literal) => literal.into(),
|
Value::Literal(literal) => literal.into(),
|
||||||
Value::Identifier(identifier) => {
|
Value::Identifier(identifier) => {
|
||||||
let value = memory.get(&identifier.name, identifier.into())?;
|
let value = memory.get(&identifier.name, identifier.into())?;
|
||||||
@ -991,7 +1006,7 @@ impl CallExpression {
|
|||||||
if fn_args.len() != function_expression.params.len() {
|
if fn_args.len() != function_expression.params.len() {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Expected {} arguments, got {}",
|
"this function expected {} arguments, got {}",
|
||||||
function_expression.params.len(),
|
function_expression.params.len(),
|
||||||
fn_args.len(),
|
fn_args.len(),
|
||||||
),
|
),
|
||||||
@ -1607,6 +1622,7 @@ impl ArrayExpression {
|
|||||||
for element in &self.elements {
|
for element in &self.elements {
|
||||||
let result = match element {
|
let result = match element {
|
||||||
Value::Literal(literal) => literal.into(),
|
Value::Literal(literal) => literal.into(),
|
||||||
|
Value::None(none) => none.into(),
|
||||||
Value::Identifier(identifier) => {
|
Value::Identifier(identifier) => {
|
||||||
let value = memory.get(&identifier.name, identifier.into())?;
|
let value = memory.get(&identifier.name, identifier.into())?;
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -1759,6 +1775,7 @@ impl ObjectExpression {
|
|||||||
for property in &self.properties {
|
for property in &self.properties {
|
||||||
let result = match &property.value {
|
let result = match &property.value {
|
||||||
Value::Literal(literal) => literal.into(),
|
Value::Literal(literal) => literal.into(),
|
||||||
|
Value::None(none) => none.into(),
|
||||||
Value::Identifier(identifier) => {
|
Value::Identifier(identifier) => {
|
||||||
let value = memory.get(&identifier.name, identifier.into())?;
|
let value = memory.get(&identifier.name, identifier.into())?;
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -2646,6 +2663,23 @@ impl FunctionExpression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Required parameters must be declared before optional parameters.
|
||||||
|
/// This gets all the required parameters.
|
||||||
|
pub fn required_params(&self) -> &[Parameter] {
|
||||||
|
let end_of_required_params = self
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.position(|param| param.optional)
|
||||||
|
// If there's no optional params, then all the params are required params.
|
||||||
|
.unwrap_or(self.params.len());
|
||||||
|
&self.params[..end_of_required_params]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimum and maximum number of arguments this function can take.
|
||||||
|
pub fn number_of_args(&self) -> RangeInclusive<usize> {
|
||||||
|
self.required_params().len()..=self.params.len()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Value) {
|
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Value) {
|
||||||
self.body.replace_value(source_range, new_value);
|
self.body.replace_value(source_range, new_value);
|
||||||
}
|
}
|
||||||
@ -3400,4 +3434,107 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn required_params() {
|
||||||
|
for (i, (test_name, expected, function_expr)) in [
|
||||||
|
(
|
||||||
|
"no params",
|
||||||
|
(0..=0),
|
||||||
|
FunctionExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
params: vec![],
|
||||||
|
body: Program {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: Vec::new(),
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all required params",
|
||||||
|
(1..=1),
|
||||||
|
FunctionExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
params: vec![Parameter {
|
||||||
|
identifier: Identifier {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: "foo".to_owned(),
|
||||||
|
},
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
body: Program {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: Vec::new(),
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all optional params",
|
||||||
|
(0..=1),
|
||||||
|
FunctionExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
params: vec![Parameter {
|
||||||
|
identifier: Identifier {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: "foo".to_owned(),
|
||||||
|
},
|
||||||
|
optional: true,
|
||||||
|
}],
|
||||||
|
body: Program {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: Vec::new(),
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params",
|
||||||
|
(1..=2),
|
||||||
|
FunctionExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
params: vec![
|
||||||
|
Parameter {
|
||||||
|
identifier: Identifier {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: "foo".to_owned(),
|
||||||
|
},
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
Parameter {
|
||||||
|
identifier: Identifier {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: "bar".to_owned(),
|
||||||
|
},
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: Program {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: Vec::new(),
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let actual = function_expr.number_of_args();
|
||||||
|
assert_eq!(expected, actual, "failed test #{i} '{test_name}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
src/wasm-lib/kcl/src/ast/types/none.rs
Normal file
58
src/wasm-lib/kcl/src/ast/types/none.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//! KCL has optional parameters. Their type is [`KclOption`].
|
||||||
|
//! If an optional parameter is not given, it will have a value of type [`KclNone`].
|
||||||
|
use databake::*;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::executor::{MemoryItem, SourceRange, UserVal};
|
||||||
|
|
||||||
|
use super::ConstraintLevel;
|
||||||
|
|
||||||
|
/// KCL value for an optional parameter which was not given an argument.
|
||||||
|
/// (remember, parameters are in the function declaration,
|
||||||
|
/// arguments are in the function call/application).
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default)]
|
||||||
|
#[databake(path = kcl_lib::ast::types)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub struct KclNone {
|
||||||
|
// TODO: Convert this to be an Option<SourceRange>.
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&KclNone> for SourceRange {
|
||||||
|
fn from(v: &KclNone) -> Self {
|
||||||
|
Self([v.start, v.end])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&KclNone> for UserVal {
|
||||||
|
fn from(none: &KclNone) -> Self {
|
||||||
|
UserVal {
|
||||||
|
value: serde_json::to_value(none).expect("can always serialize a None"),
|
||||||
|
meta: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&KclNone> for MemoryItem {
|
||||||
|
fn from(none: &KclNone) -> Self {
|
||||||
|
let val = UserVal::from(none);
|
||||||
|
MemoryItem::UserVal(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KclNone {
|
||||||
|
pub fn source_range(&self) -> SourceRange {
|
||||||
|
SourceRange([self.start, self.end])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the constraint level.
|
||||||
|
/// KCL None is never constrained.
|
||||||
|
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||||
|
ConstraintLevel::None {
|
||||||
|
source_ranges: vec![self.source_range()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
|||||||
|
|
||||||
use crate::executor::SourceRange;
|
use crate::executor::SourceRange;
|
||||||
|
|
||||||
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone)]
|
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum KclError {
|
pub enum KclError {
|
||||||
@ -32,7 +32,7 @@ pub enum KclError {
|
|||||||
Internal(KclErrorDetails),
|
Internal(KclErrorDetails),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone)]
|
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct KclErrorDetails {
|
pub struct KclErrorDetails {
|
||||||
#[serde(rename = "sourceRanges")]
|
#[serde(rename = "sourceRanges")]
|
||||||
|
@ -8,10 +8,11 @@ use kittycad::types::{Color, ModelingCmd, Point3D};
|
|||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value as JValue;
|
||||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{BodyItem, FunctionExpression, Value},
|
ast::types::{BodyItem, FunctionExpression, KclNone, Value},
|
||||||
engine::{EngineConnection, EngineManager},
|
engine::{EngineConnection, EngineManager},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
std::{FunctionKind, StdLib},
|
std::{FunctionKind, StdLib},
|
||||||
@ -49,6 +50,7 @@ impl ProgramMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a value from the program memory.
|
/// Get a value from the program memory.
|
||||||
|
/// Return Err if not found.
|
||||||
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
||||||
self.root.get(key).ok_or_else(|| {
|
self.root.get(key).ok_or_else(|| {
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
@ -349,6 +351,40 @@ impl MemoryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a JSON value and deserialize it into some concrete type.
|
||||||
|
pub fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> {
|
||||||
|
let json = self.get_json_value()?;
|
||||||
|
|
||||||
|
serde_json::from_value(json).map_err(|e| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||||
|
source_ranges: self.clone().into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a JSON value and deserialize it into some concrete type.
|
||||||
|
/// If it's a KCL None, return None. Otherwise return Some.
|
||||||
|
pub fn get_json_opt<T: serde::de::DeserializeOwned>(&self) -> Result<Option<T>, KclError> {
|
||||||
|
let json = self.get_json_value()?;
|
||||||
|
if let JValue::Object(ref o) = json {
|
||||||
|
if let Some(JValue::String(s)) = o.get("type") {
|
||||||
|
if s == "KclNone" {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::from_value(json)
|
||||||
|
.map_err(|e| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||||
|
source_ranges: self.clone().into(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(Some)
|
||||||
|
}
|
||||||
|
|
||||||
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
||||||
/// If it's not a function, return Err.
|
/// If it's not a function, return Err.
|
||||||
pub async fn call_fn(
|
pub async fn call_fn(
|
||||||
@ -873,6 +909,9 @@ pub async fn execute(
|
|||||||
let metadata = Metadata { source_range };
|
let metadata = Metadata { source_range };
|
||||||
|
|
||||||
match &declaration.init {
|
match &declaration.init {
|
||||||
|
Value::None(none) => {
|
||||||
|
memory.add(&var_name, none.into(), source_range)?;
|
||||||
|
}
|
||||||
Value::Literal(literal) => {
|
Value::Literal(literal) => {
|
||||||
memory.add(&var_name, literal.into(), source_range)?;
|
memory.add(&var_name, literal.into(), source_range)?;
|
||||||
}
|
}
|
||||||
@ -892,27 +931,8 @@ pub async fn execute(
|
|||||||
_metadata: Vec<Metadata>,
|
_metadata: Vec<Metadata>,
|
||||||
ctx: ExecutorContext| {
|
ctx: ExecutorContext| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut fn_memory = memory.clone();
|
let mut fn_memory =
|
||||||
|
assign_args_to_params(&function_expression, args, memory.clone())?;
|
||||||
if args.len() != function_expression.params.len() {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"Expected {} arguments, got {}",
|
|
||||||
function_expression.params.len(),
|
|
||||||
args.len(),
|
|
||||||
),
|
|
||||||
source_ranges: vec![(&function_expression).into()],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the arguments to the memory.
|
|
||||||
for (index, param) in function_expression.params.iter().enumerate() {
|
|
||||||
fn_memory.add(
|
|
||||||
¶m.identifier.name,
|
|
||||||
args.get(index).unwrap().clone(),
|
|
||||||
(¶m.identifier).into(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = execute(
|
let result = execute(
|
||||||
function_expression.body.clone(),
|
function_expression.body.clone(),
|
||||||
@ -1010,6 +1030,9 @@ pub async fn execute(
|
|||||||
}
|
}
|
||||||
Value::PipeSubstitution(_) => {}
|
Value::PipeSubstitution(_) => {}
|
||||||
Value::FunctionExpression(_) => {}
|
Value::FunctionExpression(_) => {}
|
||||||
|
Value::None(none) => {
|
||||||
|
memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none)));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1017,10 +1040,67 @@ pub async fn execute(
|
|||||||
Ok(memory.clone())
|
Ok(memory.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For each argument given,
|
||||||
|
/// assign it to a parameter of the function, in the given block of function memory.
|
||||||
|
/// Returns Err if too few/too many arguments were given for the function.
|
||||||
|
fn assign_args_to_params(
|
||||||
|
function_expression: &FunctionExpression,
|
||||||
|
args: Vec<MemoryItem>,
|
||||||
|
mut fn_memory: ProgramMemory,
|
||||||
|
) -> Result<ProgramMemory, KclError> {
|
||||||
|
let num_args = function_expression.number_of_args();
|
||||||
|
let (min_params, max_params) = num_args.into_inner();
|
||||||
|
let n = args.len();
|
||||||
|
|
||||||
|
// Check if the user supplied too many arguments
|
||||||
|
// (we'll check for too few arguments below).
|
||||||
|
let err_wrong_number_args = KclError::Semantic(KclErrorDetails {
|
||||||
|
message: if min_params == max_params {
|
||||||
|
format!("Expected {min_params} arguments, got {n}")
|
||||||
|
} else {
|
||||||
|
format!("Expected {min_params}-{max_params} arguments, got {n}")
|
||||||
|
},
|
||||||
|
source_ranges: vec![function_expression.into()],
|
||||||
|
});
|
||||||
|
if n > max_params {
|
||||||
|
return Err(err_wrong_number_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the arguments to the memory.
|
||||||
|
for (index, param) in function_expression.params.iter().enumerate() {
|
||||||
|
if let Some(arg) = args.get(index) {
|
||||||
|
// Argument was provided.
|
||||||
|
fn_memory.add(¶m.identifier.name, arg.clone(), (¶m.identifier).into())?;
|
||||||
|
} else {
|
||||||
|
// Argument was not provided.
|
||||||
|
if param.optional {
|
||||||
|
// If the corresponding parameter is optional,
|
||||||
|
// then it's fine, the user doesn't need to supply it.
|
||||||
|
let none = KclNone {
|
||||||
|
start: param.identifier.start,
|
||||||
|
end: param.identifier.end,
|
||||||
|
};
|
||||||
|
fn_memory.add(
|
||||||
|
¶m.identifier.name,
|
||||||
|
MemoryItem::from(&none),
|
||||||
|
(¶m.identifier).into(),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// But if the corresponding parameter was required,
|
||||||
|
// then the user has called with too few arguments.
|
||||||
|
return Err(err_wrong_number_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(fn_memory)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::ast::types::{Identifier, Parameter};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
||||||
@ -1566,4 +1646,122 @@ show(bracket)
|
|||||||
"#;
|
"#;
|
||||||
parse_execute(ast).await.unwrap();
|
parse_execute(ast).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assign_args_to_params() {
|
||||||
|
// Set up a little framework for this test.
|
||||||
|
fn mem(number: usize) -> MemoryItem {
|
||||||
|
MemoryItem::UserVal(UserVal {
|
||||||
|
value: number.into(),
|
||||||
|
meta: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn ident(s: &'static str) -> Identifier {
|
||||||
|
Identifier {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
name: s.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn opt_param(s: &'static str) -> Parameter {
|
||||||
|
Parameter {
|
||||||
|
identifier: ident(s),
|
||||||
|
optional: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn req_param(s: &'static str) -> Parameter {
|
||||||
|
Parameter {
|
||||||
|
identifier: ident(s),
|
||||||
|
optional: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Declare the test cases.
|
||||||
|
for (test_name, params, args, expected) in [
|
||||||
|
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())),
|
||||||
|
(
|
||||||
|
"all params required, and all given, should be OK",
|
||||||
|
vec![req_param("x")],
|
||||||
|
vec![mem(1)],
|
||||||
|
Ok(ProgramMemory {
|
||||||
|
return_: None,
|
||||||
|
root: HashMap::from([("x".to_owned(), mem(1))]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all params required, none given, should error",
|
||||||
|
vec![req_param("x")],
|
||||||
|
vec![],
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![SourceRange([0, 0])],
|
||||||
|
message: "Expected 1 arguments, got 0".to_owned(),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all params optional, none given, should be OK",
|
||||||
|
vec![opt_param("x")],
|
||||||
|
vec![],
|
||||||
|
Ok(ProgramMemory {
|
||||||
|
return_: None,
|
||||||
|
root: HashMap::from([("x".to_owned(), MemoryItem::from(&KclNone::default()))]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, too few given",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![],
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![SourceRange([0, 0])],
|
||||||
|
message: "Expected 1-2 arguments, got 0".to_owned(),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, minimum given, should be OK",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![mem(1)],
|
||||||
|
Ok(ProgramMemory {
|
||||||
|
return_: None,
|
||||||
|
root: HashMap::from([
|
||||||
|
("x".to_owned(), mem(1)),
|
||||||
|
("y".to_owned(), MemoryItem::from(&KclNone::default())),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, maximum given, should be OK",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![mem(1), mem(2)],
|
||||||
|
Ok(ProgramMemory {
|
||||||
|
return_: None,
|
||||||
|
root: HashMap::from([("x".to_owned(), mem(1)), ("y".to_owned(), mem(2))]),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, too many given",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![mem(1), mem(2), mem(3)],
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![SourceRange([0, 0])],
|
||||||
|
message: "Expected 1-2 arguments, got 3".to_owned(),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
// Run each test.
|
||||||
|
let func_expr = &FunctionExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
params,
|
||||||
|
body: crate::ast::types::Program {
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
body: Vec::new(),
|
||||||
|
non_code_meta: Default::default(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
|
||||||
|
assert_eq!(
|
||||||
|
actual, expected,
|
||||||
|
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -299,6 +299,15 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
|
|||||||
message: TODO_783.to_owned(),
|
message: TODO_783.to_owned(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
Value::None(_) => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
// TODO: Better error message here.
|
||||||
|
// Once we have ways to use None values (e.g. by replacing with a default value)
|
||||||
|
// we should suggest one of them here.
|
||||||
|
message: "cannot use a KCL None value as an operand".to_owned(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
Value::UnaryExpression(x) => BinaryPart::UnaryExpression(x),
|
Value::UnaryExpression(x) => BinaryPart::UnaryExpression(x),
|
||||||
Value::Literal(x) => BinaryPart::Literal(x),
|
Value::Literal(x) => BinaryPart::Literal(x),
|
||||||
Value::Identifier(x) => BinaryPart::Identifier(x),
|
Value::Identifier(x) => BinaryPart::Identifier(x),
|
||||||
|
@ -154,6 +154,7 @@ impl Default for StdLib {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum FunctionKind {
|
pub enum FunctionKind {
|
||||||
Core(Box<dyn StdLibFn>),
|
Core(Box<dyn StdLibFn>),
|
||||||
Std(Box<dyn KclStdLibFn>),
|
Std(Box<dyn KclStdLibFn>),
|
||||||
|
@ -6,6 +6,7 @@ use kittycad::types::{Angle, ModelingCmd, Point3D};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::executor::SourceRange;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
@ -1122,7 +1123,7 @@ async fn inner_tangential_arc(
|
|||||||
path: sketch_group.id,
|
path: sketch_group.id,
|
||||||
segment: kittycad::types::PathSegment::TangentialArc {
|
segment: kittycad::types::PathSegment::TangentialArc {
|
||||||
radius: *radius,
|
radius: *radius,
|
||||||
offset: kittycad::types::Angle {
|
offset: Angle {
|
||||||
unit: kittycad::types::UnitAngle::Degrees,
|
unit: kittycad::types::UnitAngle::Degrees,
|
||||||
value: *offset,
|
value: *offset,
|
||||||
},
|
},
|
||||||
@ -1178,27 +1179,32 @@ fn tan_arc_to(sketch_group: &SketchGroup, to: &[f64; 2]) -> ModelingCmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data to draw a tangential arc to a specific point.
|
fn too_few_args(source_range: SourceRange) -> KclError {
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
KclError::Syntax(KclErrorDetails {
|
||||||
#[ts(export)]
|
source_ranges: vec![source_range],
|
||||||
#[serde(rename_all = "camelCase", untagged)]
|
message: "too few arguments".to_owned(),
|
||||||
pub enum TangentialArcToData {
|
})
|
||||||
/// A point with a tag.
|
}
|
||||||
PointWithTag {
|
|
||||||
/// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
|
fn get_arg<I: Iterator>(it: &mut I, src: SourceRange) -> Result<I::Item, KclError> {
|
||||||
to: [f64; 2],
|
it.next().ok_or_else(|| too_few_args(src))
|
||||||
/// The tag.
|
|
||||||
tag: String,
|
|
||||||
},
|
|
||||||
/// A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
|
|
||||||
Point([f64; 2]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a tangential arc to a specific point.
|
/// Draw a tangential arc to a specific point.
|
||||||
pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> {
|
pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
let (data, sketch_group): (TangentialArcToData, Box<SketchGroup>) = args.get_data_and_sketch_group()?;
|
let src = args.source_range;
|
||||||
|
|
||||||
let new_sketch_group = inner_tangential_arc_to(data, sketch_group, args).await?;
|
// Get arguments to function call
|
||||||
|
let mut it = args.args.iter();
|
||||||
|
let to: [f64; 2] = get_arg(&mut it, src)?.get_json()?;
|
||||||
|
let sketch_group: Box<SketchGroup> = get_arg(&mut it, src)?.get_json()?;
|
||||||
|
let tag = if let Ok(memory_item) = get_arg(&mut it, src) {
|
||||||
|
memory_item.get_json_opt()?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_sketch_group = inner_tangential_arc_to(to, sketch_group, tag, args).await?;
|
||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1207,29 +1213,23 @@ pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "tangentialArcTo",
|
name = "tangentialArcTo",
|
||||||
}]
|
}]
|
||||||
async fn inner_tangential_arc_to(
|
async fn inner_tangential_arc_to(
|
||||||
data: TangentialArcToData,
|
to: [f64; 2],
|
||||||
sketch_group: Box<SketchGroup>,
|
sketch_group: Box<SketchGroup>,
|
||||||
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
||||||
let to = match &data {
|
let [to_x, to_y] = to;
|
||||||
TangentialArcToData::PointWithTag { to, .. } => to,
|
|
||||||
TangentialArcToData::Point(to) => to,
|
|
||||||
};
|
|
||||||
|
|
||||||
let delta = [to[0] - from.x, to[1] - from.y];
|
let delta = [to_x - from.x, to_y - from.y];
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
args.send_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
|
args.send_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
|
||||||
|
|
||||||
let current_path = Path::ToPoint {
|
let current_path = Path::ToPoint {
|
||||||
base: BasePath {
|
base: BasePath {
|
||||||
from: from.into(),
|
from: from.into(),
|
||||||
to: *to,
|
to,
|
||||||
name: if let TangentialArcToData::PointWithTag { tag, .. } = data {
|
name: tag.unwrap_or_default(),
|
||||||
tag.to_string()
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
},
|
|
||||||
geo_meta: GeoMeta {
|
geo_meta: GeoMeta {
|
||||||
id,
|
id,
|
||||||
metadata: args.source_range.into(),
|
metadata: args.source_range.into(),
|
||||||
|
@ -474,6 +474,24 @@ show(square)
|
|||||||
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999);
|
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn optional_params() {
|
||||||
|
let code = r#"
|
||||||
|
fn circle = (pos, radius, tag?) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt(pos, %)
|
||||||
|
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
show(circle([2, 2], 20))
|
||||||
|
"#;
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_rounded_with_holes() {
|
async fn serial_test_rounded_with_holes() {
|
||||||
let code = r#"fn circle = (pos, radius) => {
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
@ -488,17 +506,21 @@ async fn serial_test_rounded_with_holes() {
|
|||||||
return sg
|
return sg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tarc = (to, sketchGroup, tag?) => {
|
||||||
|
return tangentialArcTo(to, sketchGroup, tag)
|
||||||
|
}
|
||||||
|
|
||||||
fn roundedRectangle = (pos, w, l, cornerRadius) => {
|
fn roundedRectangle = (pos, w, l, cornerRadius) => {
|
||||||
const rr = startSketchOn('XY')
|
const rr = startSketchOn('XY')
|
||||||
|> startProfileAt([pos[0] - w/2, 0], %)
|
|> startProfileAt([pos[0] - w/2, 0], %)
|
||||||
|> lineTo([pos[0] - w/2, pos[1] - l/2 + cornerRadius], %)
|
|> lineTo([pos[0] - w/2, pos[1] - l/2 + cornerRadius], %)
|
||||||
|> tangentialArcTo([pos[0] - w/2 + cornerRadius, pos[1] - l/2], %)
|
|> tarc([pos[0] - w/2 + cornerRadius, pos[1] - l/2], %, "arc0")
|
||||||
|> lineTo([pos[0] + w/2 - cornerRadius, pos[1] - l/2], %)
|
|> lineTo([pos[0] + w/2 - cornerRadius, pos[1] - l/2], %)
|
||||||
|> tangentialArcTo([pos[0] + w/2, pos[1] - l/2 + cornerRadius], %)
|
|> tarc([pos[0] + w/2, pos[1] - l/2 + cornerRadius], %)
|
||||||
|> lineTo([pos[0] + w/2, pos[1] + l/2 - cornerRadius], %)
|
|> lineTo([pos[0] + w/2, pos[1] + l/2 - cornerRadius], %)
|
||||||
|> tangentialArcTo([pos[0] + w/2 - cornerRadius, pos[1] + l/2], %)
|
|> tarc([pos[0] + w/2 - cornerRadius, pos[1] + l/2], %, "arc2")
|
||||||
|> lineTo([pos[0] - w/2 + cornerRadius, pos[1] + l/2], %)
|
|> lineTo([pos[0] - w/2 + cornerRadius, pos[1] + l/2], %)
|
||||||
|> tangentialArcTo([pos[0] - w/2, pos[1] + l/2 - cornerRadius], %)
|
|> tarc([pos[0] - w/2, pos[1] + l/2 - cornerRadius], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
return rr
|
return rr
|
||||||
}
|
}
|
||||||
|
BIN
src/wasm-lib/tests/executor/outputs/optional_params.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/optional_params.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
Reference in New Issue
Block a user